How to create an Modbus-TCP alarm status dashboard

My goal is to motorize 350 addresses of a PLC over a modebus-tcp, and if any of the values are different then 0 (zero) to display on a dashboard those address, the corresponding status (based on the value reported) and the trigger time.
In other words to create an alarm dashboard that shows in real time the PLC status.

Some clarification:

  • The flow only pulls modbus-tcp data (every second) no need to send any commands (values)
  • Each address can report only the following values:
    - "0" - normal state (value not displayed)
    - "2" - not acknowledge alarm (display in red)
    - "1027" - acknowledge alarm (display in yellow)
    - "1088" - sensor fail (sensor fail)
  • Each address is associated with a certain area of the system (ex. 0001 = valve 1; 0002 = light1; 0003 = main computer .....) on the dashboard I want to display the associated name of each address too.
  • beside the real time dashboard I want to create an alarm log (a database with all reported alarms as they occur)

I need some help with he following:

  • how to display the initial triggered alarm time (on my flow time refreshes with every data pull)
  • how to separate the triggered alarms from the modbus-read array and to use a database to associate the names of the triggered address (for all 350 of them)
  • how to separate the triggered alarms from the modbus-read array to be logged to a database

Any help is much appreciated.

Here is a print screen of my dashboard and the flow I have so far:

[{"id":"e36f3a9f.a38dd8","type":"ui_table","z":"74d8f714.ab8378","group":"81dd3718.b97888","name":"Alarm","order":1,"width":"12","height":"8","columns":[{"field":"Time","title":"<center>Time</>","width":"25%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Register","title":"<center>Channel</>","width":"10%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Notes","title":"<center>Alarm Message</>","width":"50%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}},{"field":"Alarm","title":"<center>Status</>","width":"15%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":850,"y":1460,"wires":[]},{"id":"80183847.7650d8","type":"modbus-read","z":"74d8f714.ab8378","name":"","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"","dataType":"HoldingRegister","adr":"0","quantity":"100","rate":"5","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"c6527b9f.17c448","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":170,"y":1480,"wires":[["c37bbf47.cf40d8","6cd67e6f.8345f8","55ab041b.5d0f3c","2856eb17.df3bb4","bb3c74a1.d7b4c","74cfddeb.27a7e4","7fbc0c6f.1603dc","345beee5.52173a"],[]]},{"id":"6cd67e6f.8345f8","type":"function","z":"74d8f714.ab8378","name":"1-100 test all alarms code","func":"\nvar t = new Date();\nt = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + ' / ' + ('00' + (t.getMonth()+1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());\n\n//var i=0;\nfor (var i=0; i < msg.payload.length ; i++) {\n\nif (msg.payload[i]!==0) {\n    if (i<9) {\n        if (msg.payload[i]===2) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"0000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1027) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"0000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1028) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"0000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1029) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"0000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }        \n        if (msg.payload[i]===1088) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"0000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }        \n    }\n    if (i>=9 && i<99) {\n        if (msg.payload[i]===2) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1027) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1028) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1029) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }        \n        if (msg.payload[i]===1088) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"000\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }    \n    }\n    if (i==99) {\n        if (msg.payload[i]===2) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"00\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1027) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"00\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1028) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"00\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }\n        if (msg.payload[i]===1029) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"00\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }        \n        if (msg.payload[i]===1088) {\n            msg.payload.push(\n                {\n                    \"Time\": t,\n                    \"Register\":\"00\" + (i+1),\n                    \"Alarm\": \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\",\n                    \"Notes\": \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i+1) +\"</div>\"\n                })\n        }     \n   }\n}\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":430,"y":1460,"wires":[["8bb0330c.318e2","42560203.14ce24","a8504d6b.fc97f","5285815a.bef9b","f4cbbe3f.7cd658"]]},{"id":"42560203.14ce24","type":"function","z":"74d8f714.ab8378","name":"","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":680,"y":1460,"wires":[["e36f3a9f.a38dd8"]]},{"id":"81dd3718.b97888","type":"ui_group","name":"Active Alarms","tab":"4e11db25.239e94","order":1,"disp":true,"width":"12","collapse":true},{"id":"c6527b9f.17c448","type":"modbus-client","name":"","clienttype":"tcp","bufferCommands":false,"stateLogEnabled":false,"queueLogEnabled":false,"tcpHost":"192.168.1.120","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":1,"commandDelay":1,"clientTimeout":1000,"reconnectOnTimeout":true,"reconnectTimeout":2000,"parallelUnitIdsAllowed":true},{"id":"4e11db25.239e94","type":"ui_tab","name":"Alarms Monitiring","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Essentially, store the modbus array (payload) in context & check its value is different before generating an alarm entry. Also, store the table rows array in context and add/remove/update the rows depending on the change from current to previous value.

e.g...


var t = new Date();
t = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + ' / ' + ('00' + (t.getMonth() + 1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());

var previous = context.get("previous") || [];
var current = [...msg.payload];
var table = context.get("table") || [];

for (var i = 0; i < msg.payload.length; i++) {
    var register = ("" + (i + 1)).padStart(5,"0");
    
    //has value changed?
    if (previous[i] !== current[i]) {
        var row = table.find(e => e.Register == register);

        if (!row) {
            if (current[i] === 0)
                continue; //dont add this one (its 0)
            
            row = {
                "Register": register,
                "Alarm": "",
                "Notes": ""
            }            
            table.push(row);
        } else {
            //there is a row in the table...
            if (current[i] === 0) {
                ///remove it because its now 0
                table = table.filter(e => e.Register !== register);
                continue;
            }
        }

        row.Time = t;//update the time
        if (current[i] === 2) {
            row.Alarm = "<div style='background-color: red; width:100%; color:white'>" + "ALARM" + "</div>";
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>";
        } else if (current[i] === 1027) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>";
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>";
        } else if (current[i] === 1028) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>",
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>"
        } else if (current[i] === 1029) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>",
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>"
        } else if (current[i] === 1088) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "SENSOR FAIL" + "</div>",
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>"
        }        
    }
}

context.set("previous", current);
context.set("table", table);

msg.payload = table;
return msg;

Here is a demo flow...

[{"id":"e36f3a9f.a38dd8","type":"ui_table","z":"b872cb4b.5a6448","group":"81dd3718.b97888","name":"Alarm","order":1,"width":"12","height":"8","columns":[{"field":"Time","title":"<center>Time</>","width":"25%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Register","title":"<center>Channel</>","width":"10%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Notes","title":"<center>Alarm Message</>","width":"50%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}},{"field":"Alarm","title":"<center>Status</>","width":"15%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":1310,"y":1000,"wires":[]},{"id":"6cd67e6f.8345f8","type":"function","z":"b872cb4b.5a6448","name":"generate alarm table","func":"\nvar t = new Date();\nt = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + ' / ' + ('00' + (t.getMonth() + 1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());\n\nvar previous = context.get(\"previous\") || [];\nvar current = [...msg.payload];\nvar table = context.get(\"table\") || [];\nfor (var i = 0; i < msg.payload.length; i++) {\n    var register = (\"\" + (i + 1)).padStart(5,\"0\");\n    \n    //has value changed?\n    if (previous[i] !== current[i]) {\n        var row = table.find(e => e.Register == register);\n\n        if (!row) {\n            if (current[i] === 0)\n                continue; //dont add this one (its 0)\n            \n            row = {\n                \"Register\": register,\n                \"Alarm\": \"\",\n                \"Notes\": \"\"\n            }            \n            table.push(row);\n        } else {\n            //there is a row in the table...\n            if (current[i] === 0) {\n                ///remove it because its now 0\n                table = table.filter(e => e.Register !== register);\n                continue;\n            }\n        }\n\n        row.Time = t;//update the time\n        if (current[i] === 2) {\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\";\n            row.Notes = \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i + 1) + \"</div>\";\n        } else if (current[i] === 1027) {\n            row.Alarm = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Notes = \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i + 1) + \"</div>\";\n        } else if (current[i] === 1028) {\n            row.Alarm = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n            row.Notes = \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i + 1) + \"</div>\"\n        } else if (current[i] === 1029) {\n            row.Alarm = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\",\n            row.Notes = \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i + 1) + \"</div>\"\n        } else if (current[i] === 1088) {\n            row.Alarm = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\",\n            row.Notes = \"<div style='background-color: red; width:100%; color:white'>\" + \"NO COMM. ON MAIN NETWORK\" + (i + 1) + \"</div>\"\n        }        \n    }\n}\n\ncontext.set(\"previous\", current);\ncontext.set(\"table\", table);\n\nmsg.payload = table;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1120,"y":1000,"wires":[["e36f3a9f.a38dd8"]]},{"id":"4cb90eb.e1c59f","type":"inject","z":"b872cb4b.5a6448","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[0,0,0,0,0]","payloadType":"json","x":790,"y":1000,"wires":[["6cd67e6f.8345f8"]]},{"id":"d525bd3.ce2364","type":"inject","z":"b872cb4b.5a6448","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[0,0,0,0,2]","payloadType":"json","x":800,"y":1040,"wires":[["6cd67e6f.8345f8"]]},{"id":"1e32cbd0.0b6174","type":"inject","z":"b872cb4b.5a6448","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[0,2,0,0,2]","payloadType":"json","x":800,"y":1080,"wires":[["6cd67e6f.8345f8"]]},{"id":"5fa3b332.bb05dc","type":"inject","z":"b872cb4b.5a6448","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[0,1027,0,0,2]","payloadType":"json","x":810,"y":1120,"wires":[["6cd67e6f.8345f8"]]},{"id":"7b341dc5.0d6854","type":"inject","z":"b872cb4b.5a6448","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1028,1027,0,0,2]","payloadType":"json","x":820,"y":1160,"wires":[["6cd67e6f.8345f8"]]},{"id":"81dd3718.b97888","type":"ui_group","name":"Active Alarms","tab":"4e11db25.239e94","order":1,"disp":true,"width":"12","collapse":true},{"id":"4e11db25.239e94","type":"ui_tab","name":"Alarms Monitiring","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

For this part, use a lookup...

var lookup = {
  1: "Door Sensor",
  2: "Window Sensor"
}
var sensorNo = 1;
var sensorName = lookup[sensorNo] || ("Unknown " + sensorNo);

proof....
image

Do you know SQL and databases?

what database?

There are hundreds of examples of INSERTing data into SQL databases.

I suggest you start with separate demo flows (using injects) to get it node-red --> database working THEN try to integrate with your flow.

Thanks for your quick reply,
I'll check your suggestions and get back with the results.

Thanks for sharing the above code examples, they worked great and the result in the Alarm Dashboard is as I want it.

For the alarms log I want to use a SQlite database, I've seen some examples that how to create a table and how to insert data, but I don't know how to extract each objects from the generated alarms array.

I adapt an example to my flow but I don't know how to extract those payloads.

msg.topic = "INSERT INTO accesslog (time,register,alarm,notes) " +
        "VALUES ('"+msg.payload.time+"','"+msg.payload.register+"','"+msg.payload.alarm+"','"+msg.payload.notes+"')";

first things first, JavaScript is case sensitive (e.g. msg.payload.time is different to msg.payload.Time)


Am I right in thinking that you want the "detected alarm changes (the adds and updates)" to be sent to SQLite database?

Then you could simply send a msg from the function that updates the alarm table. For this you use node.send() and if you use an array you can send out of different outputs on the function. Port 1 could be for the final table data, port 2 for sending to SQLite

e.g...
set the function to output 2 outputs then return the table out of output 1 (at the end of the function) and node.send the "update/insert" messages out of output 2 (in the middle of the loop as changes are detected)...

var t = new Date();
t = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + ' / ' + ('00' + (t.getMonth() + 1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());

var previous = context.get("previous") || [];
var current = [...msg.payload];
var table = context.get("table") || [];

for (var i = 0; i < msg.payload.length; i++) {
    var register = ("" + (i + 1)).padStart(5,"0");
    var update_or_insert = "";

    //has value changed?
    if (previous[i] !== current[i]) {
        var row = table.find(e => e.Register == register);

        if (!row) {
            if (current[i] === 0)
                continue; //dont add this one (its 0)
            
            row = {
                "Register": register,
                "Alarm": "",
                "Notes": ""
            }            
            update_or_insert = "insert";
            table.push(row);
        } else {
            //there is a row in the table...
            if (current[i] === 0) {
                ///remove it because its now 0
                table = table.filter(e => e.Register !== register);
                continue;
            }
            update_or_insert = "update";
        }

        row.Time = t;//update the time
        if (current[i] === 2) {
            row.Alarm = "<div style='background-color: red; width:100%; color:white'>" + "ALARM" + "</div>";
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>";
        } else if (current[i] === 1027) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>";
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>";
        } else if (current[i] === 1028) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>",
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>"
        } else if (current[i] === 1029) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>",
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>"
        } else if (current[i] === 1088) {
            row.Alarm = "<div style='background-color: yellow; width:100%; color:red'>" + "SENSOR FAIL" + "</div>",
            row.Notes = "<div style='background-color: red; width:100%; color:white'>" + "NO COMM. ON MAIN NETWORK" + (i + 1) + "</div>"
        }       
        //TODO: 
        // You can create the SQL here and send it in the topic OR send an object with 
        // the clean alarm and notes info (i.e. no HTML) then build a SQL query in the next node 
        node.send( [null, {topic:update_or_insert,payload:row}] ); //Send the updated ROW out of output 2
    }
}

context.set("previous", current);
context.set("table", table);

msg.payload = table;
return [msg, null]; //Return table to function output 1;

NOTE: Read the TODO in the code.

NOTE2: Read this: There’s a great page in the docs that will explain how to use the debug panel to find the right path to any data item. Working with messages : Node-RED

I believe so, I have to try it to see how data is logged.

I'll give it a try and get back with the results.

Thanks again, much appreciated your help.

Hello,
I manage to create the SQLite database and to inset the triggered alarms into the database log as suggested above.

Here is the code I adapted up to my knowledge:


var previous = context.get("previous") || [];
var current = [...msg.payload];
var table = context.get("table") || [];
for (var i = 0; i < msg.payload.length; i++) {
    var channel = ("" + (i + 1)).padStart(5,"0");
    var sensorNo = (i + 1);
    var sensorName = lookup[sensorNo] || ("Unknown " + sensorNo);
    //has value changed?
    if (previous[i] !== current[i]) {
        var row = table.find(e => e.Channel == channel);

        if (!row) {
            if (current[i] === 0)
                continue; //dont add this one (its 0)
            
            row = {
                "Time":t,
                "Channel": channel,
                "Alarm": "",                
                "Status": "",
                "Alarmlog": "",                
                "Statuslog": ""                
            }            
            table.push(row);
        } else {
            //there is a row in the table...
            if (current[i] === 0) {
                ///remove it because its now 0
                table = table.filter(e => e.Channel !== channel);
                continue;
            }
        }

        row.Time = t;//update the time
        if (current[i] === 2) {
            row.Status = "<div style='background-color: red; width:100%; color:white'>" + "ALARM" + "</div>";
            row.Alarm = "<div style='background-color: red; width:100%; color:white'>" + sensorName + "</div>";
            row.Statuslog = "ALARM !";
            row.Alarmlog = sensorName ;            
        } else if (current[i] === 1027) {
            row.Status = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>";
            row.Alarm = "<div style='background-color: red; width:100%; color:white'>" + sensorName + "</div>";
            row.Statuslog = "ALARM";
            row.Alarmlog = sensorName ;            
        } else if (current[i] === 1028) {
            row.Status = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>";
            row.Alarm = "<div style='background-color: red; width:100%; color:white'>" + sensorName + "</div>";
            row.Statuslog = "ALARM";
            row.Alarmlog = sensorName ;
        } else if (current[i] === 1029) {
            row.Status = "<div style='background-color: yellow; width:100%; color:red'>" + "ALARM" + "</div>";
            row.Alarm = "<div style='background-color: red; width:100%; color:white'>" + sensorName + "</div>";
            row.Statuslog = "ALARM";
            row.Alarmlog = sensorName ;
        } else if (current[i] === 1088) {
            row.Status = "<div style='background-color: yellow; width:100%; color:red'>" + "SENSOR FAIL" + "</div>";
            row.Alarm = "<div style='background-color: red; width:100%; color:white'>" + sensorName + "</div>";
            row.Statuslog = "SENSOR FAIL";
            row.Alarmlog = sensorName ;
        }
                //TODO: 
        // You can create the SQL here and send it in the topic OR send an object with 
        // the clean alarm and notes info (i.e. no HTML) then build a SQL query in the next node 
        node.send( [null, { topic: ("INSERT INTO alarmlog (Time,Channel,Alarm,Status) " +
        "VALUES ('"+row.Time+"','"+row.Channel+"','"+row.Alarmlog+"','"+row.Statuslog+"')")}] ); //Send the SQL query out of output 2
    }
}

context.set("previous", current);
context.set("table", table);

msg.payload = table;
return [msg, null]; //Return table to function output 1;

and the flow:

[{"id":"80183847.7650d8","type":"modbus-read","z":"74d8f714.ab8378","name":"","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"","dataType":"HoldingRegister","adr":"0","quantity":"115","rate":"5","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"c6527b9f.17c448","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":250,"y":1460,"wires":[["c37bbf47.cf40d8","b50640ea.4fca38","115b2abd.6fd2fd"],[]]},{"id":"115b2abd.6fd2fd","type":"function","z":"74d8f714.ab8378","name":"generate alarm table","func":"\nvar t = new Date();\nt = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + '/' + ('00' + (t.getMonth() + 1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());\n\nvar lookup = {\n    1: \"NO COMM. ON MAIN NETWORK\",\n    2: \"NO COMM. ON BACKUP NETWORK\",\n    3: \"MAIN SERVER OUT OF ORDER\",\n    4: \"BACKUP SERVER OUT OF ORDER\",\n    5: \"NO COMM. MAIN SERVER ON MAIN NETWORK\",\n    6: \"NO COMM. MAIN SERVER ON BACKUP NETWORK\",\n    7: \"NO COMM. BACKUP SERVER ON MAIN NETWORK\",\n    8: \"NO COMM. BACKUP SERVER ON BACKUP NETWORK\",\n    9: \"MAXIMUM DISKSPACE USED. LOGGING STOPPED\",\n    10: \"ALARMLOG: FILE CAN NOT BE WRITTEN\",\n    11: \"MTUS PORT: RD4 COM PORT FAILURE\",\n    12: \"MTUS PORT: RD4 NO COMMUNICATION\",\n    13: \"MTUS PORT: RD4 ERROR IN COMMUNICATION\",\n    14: \"MTUS STBD: RD5 COM PORT FAILURE\",\n    15: \"MTUS STBD: RD5 NO COMMUNICATION\",\n    16: \"MTUS STBD: RD5 ERROR IN COMMUNICATION\",\n    17: \"X101 NOT PRESENT\",\n    18: \"X102 NOT PRESENT\",\n    19: \"X103 NOT PRESENT\",\n    20: \"X104 NOT PRESENT\",\n    21: \"X109 NOT PRESENT\",\n    22: \"X110 NOT PRESENT\",\n    23: \"X111 NOT PRESENT\",\n    24: \"X117 NOT PRESENT\",\n    25: \"X118 NOT PRESENT\",\n    26: \"X119 NOT PRESENT\",\n    27: \"X125 NOT PRESENT\",\n    28: \"X126 NOT PRESENT\",\n    29: \"X127 NOT PRESENT\",\n    30: \"X128 NOT PRESENT\",\n    31: \"XP101 MAIN LINK NOT PRESENT\",\n    32: \"XP109 MAIN LINK NOT PRESENT\",\n    33: \"XP117 MAIN LINK NOT PRESENT\",\n    34: \"XP125 MAIN LINK NOT PRESENT\",\n    35: \"XP101 BACKUP LINK NOT PRESENT\",\n    36: \"XP109 BACKUP LINK NOT PRESENT\",\n    37: \"XP117 BACKUP LINK NOT PRESENT\",\n    38: \"XP125 BACKUP LINK NOT PRESENT\",\n    39: \"X101 INVALID BOARD TYPE\",\n    40: \"X102 INVALID BOARD TYPE\",\n    41: \"X103 INVALID BOARD TYPE\",\n    42: \"X104 INVALID BOARD TYPE\",\n    43: \"X109 INVALID BOARD TYPE\",\n    44: \"X110 INVALID BOARD TYPE\",\n    45: \"X111 INVALID BOARD TYPE\",\n    46: \"X117 INVALID BOARD TYPE\",\n    47: \"X118 INVALID BOARD TYPE\",\n    48: \"X119 INVALID BOARD TYPE\",\n    49: \"X125 INVALID BOARD TYPE\",\n    50: \"X126 INVALID BOARD TYPE\",\n    51: \"X127 INVALID BOARD TYPE\",\n    52: \"X128 INVALID BOARD TYPE\",\n    53: \"MBS_TCP: RD18 TCP/IP Socket Error\",\n    54: \"MBS_TCP: RD18 No Communication\",\n    55: \"MAIN SERVER IO SERVER NOT FOUND\",\n    56: \"BACKUP SERVER IO SERVER NOT FOUND\",\n    57: \"FB1 MAIN LINK NO COMMUNICATION\",\n    58: \"FB1 BACKUP LINK NO COMMUNICATION\",\n    59: \"FB2 MAIN LINK NO COMMUNICATION\",\n    60: \"X201 NOT PRESENT\",\n    61: \"X202 NOT PRESENT\",\n    62: \"X203 NOT PRESENT\",\n    63: \"X204 NOT PRESENT\",\n    64: \"X209 NOT PRESENT\",\n    65: \"X210 NOT PRESENT\",\n    66: \"X211 NOT PRESENT\",\n    67: \"X217 NOT PRESENT\",\n    68: \"X218 NOT PRESENT\",\n    69: \"X219 NOT PRESENT\",\n    70: \"X220 NOT PRESENT\",\n    71: \"FB2 BACKUP LINK NO COMMUNICATION\",\n    72: \"XP201 MAIN LINK NOT PRESENT\",\n    73: \"XP209 MAIN LINK NOT PRESENT\",\n    74: \"XP217 MAIN LINK NOT PRESENT\",\n    75: \"XP201 BACKUP LINK NOT PRESENT\",\n    76: \"XP209 BACKUP LINK NOT PRESENT\",\n    77: \"XP217 BACKUP LINK NOT PRESENT\",\n    78: \"X202 INVALID BOARD TYPE\",\n    79: \"X203 INVALID BOARD TYPE\",\n    80: \"X204 INVALID BOARD TYPE\",\n    81: \"X210 INVALID BOARD TYPE\",\n    82: \"X211 INVALID BOARD TYPE\",\n    83: \"X212 INVALID BOARD TYPE\",\n    84: \"X218 INVALID BOARD TYPE\",\n    85: \"X219 INVALID BOARD TYPE\",\n    86: \"X220 INVALID BOARD TYPE\",\n    87: \"X301 NOT PRESENT\",\n    88: \"X302 NOT PRESENT\",\n    89: \"X303 NOT PRESENT\",\n    90: \"X304 NOT PRESENT\",\n    91: \"X309 NOT PRESENT\",\n    92: \"XP301 MAIN LINK NOT PRESENT\",\n    93: \"XP309 MAIN LINK NOT PRESENT\",\n    94: \"XP301 BACKUP LINK NOT PRESENT\",\n    95: \"FB3 MAIN LINK NO COMMUNICATION\",\n    96: \"FB3 BACKUP LINK NO COMMUNICATION\",\n    97: \"FB3 PROC301 IEC-1131 CONFIG NOT LOADED\",\n    98: \"FB3 PROC301 IEC-1131 PROGRAM NOT RUNNING\",\n    99: \"NMEA_WIND: RD6 COM PORT ERROR\",\n    100: \"NMEA_WIND: RD6 NO COMMUNICATION\",\n    101: \"FB3 PROC309 IEC-1131 CONFIG NOT LOADED\",\n    102: \"FB3 PROC309 IEC-1131 PROGRAM NOT RUNNING\",\n    103: \"X301 INVALID BOARD TYPE\",\n    104: \"X302 INVALID BOARD TYPE\",\n    105: \"X303 INVALID BOARD TYPE\",\n    106: \"X304 INVALID BOARD TYPE\",\n    107: \"X309 INVALID BOARD TYPE\",\n    108: \"FB4 MAIN LINK NO COMMUNICATION\",\n    109: \"FB4 BACKUP LINK NO COMMUNICATION\", \n    110: \"FB4 LOP MESS NOT PRESENT\",\n    111: \"FB4 LOP ENGINEER NOT PRESENT\",\n    112: \"FB4 LOP 1ST MATE NOT PRESENT\",\n    113: \"FB4 LOP ALARMPANEL ENGINEROOM\",\n    114: \"ALARM PANEL E.R. POWER FAIL\",\n    115: \"ALARM PANEL E.R. EARTH FAULT\",\n    116: \"WATERTIGHT DOORS POWER FAILURE\",\n    117: \"WATERTIGHT DOORS BATTERY LOW\",\n    118: \"BILGE FORE PEAK LEVEL HIGH\",\n    119: \"BILGE BOW THRUSTER ROOM LEVEL HIGH\",\n    120: \"BILGE COFFERDAM LEVEL HIGH\"\n}\n\n\n\nvar previous = context.get(\"previous\") || [];\nvar current = [...msg.payload];\nvar table = context.get(\"table\") || [];\nfor (var i = 0; i < msg.payload.length; i++) {\n    var channel = (\"\" + (i + 1)).padStart(5,\"0\");\n    var sensorNo = (i + 1);\n    var sensorName = lookup[sensorNo] || (\"Unknown \" + sensorNo);\n    //has value changed?\n    if (previous[i] !== current[i]) {\n        var row = table.find(e => e.Channel == channel);\n\n        if (!row) {\n            if (current[i] === 0)\n                continue; //dont add this one (its 0)\n            \n            row = {\n                \"Time\":t,\n                \"Channel\": channel,\n                \"Alarm\": \"\",                \n                \"Status\": \"\",\n                \"Alarmlog\": \"\",                \n                \"Statuslog\": \"\"                \n            }            \n            table.push(row);\n        } else {\n            //there is a row in the table...\n            if (current[i] === 0) {\n                ///remove it because its now 0\n                table = table.filter(e => e.Channel !== channel);\n                continue;\n            }\n        }\n\n        row.Time = t;//update the time\n        if (current[i] === 2) {\n            row.Status = \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM !\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1027) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1028) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1029) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1088) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"SENSOR FAIL\";\n            row.Alarmlog = sensorName ;\n        }\n                //TODO: \n        // You can create the SQL here and send it in the topic OR send an object with \n        // the clean alarm and notes info (i.e. no HTML) then build a SQL query in the next node \n        node.send( [null, { topic: (\"INSERT INTO alarmlog (Time,Channel,Alarm,Status) \" +\n        \"VALUES ('\"+row.Time+\"','\"+row.Channel+\"','\"+row.Alarmlog+\"','\"+row.Statuslog+\"')\")}] ); //Send the SQL query out of output 2\n    }\n}\n\ncontext.set(\"previous\", current);\ncontext.set(\"table\", table);\n\nmsg.payload = table;\nreturn [msg, null]; //Return table to function output 1;","outputs":2,"noerr":0,"initialize":"","finalize":"","x":460,"y":1460,"wires":[["1caa8632.9ffda2","e36f3a9f.a38dd8"],["33eac830.bf165","25ea02b9.be8eee"]]},{"id":"e36f3a9f.a38dd8","type":"ui_table","z":"74d8f714.ab8378","group":"81dd3718.b97888","name":"Alarm List","order":1,"width":13,"height":8,"columns":[{"field":"Time","title":"<center>Time</>","width":"23%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Channel","title":"<center>Channel</>","width":"10%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Alarm","title":"<center>Alarm Message</>","width":"52%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}},{"field":"Status","title":"<center>Status</>","width":"15%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":680,"y":1440,"wires":[]},{"id":"33eac830.bf165","type":"sqlite","z":"74d8f714.ab8378","mydb":"27e5ab9a.4c829c","sqlquery":"msg.topic","sql":"","name":"Alarm Log","x":690,"y":1480,"wires":[["a37080a.2ef5","b91eacf3.d6a2b8"]]},{"id":"84f739ae.91b518","type":"ui_button","z":"74d8f714.ab8378","name":"","group":"783af07a.d61d58","order":2,"width":3,"height":1,"passthru":true,"label":"View Alarm Log","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"date","topic":"","topicType":"str","x":500,"y":1600,"wires":[["25ea02b9.be8eee"]]},{"id":"c17826fc.b4e2","type":"inject","z":"74d8f714.ab8378","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":520,"y":1640,"wires":[["25ea02b9.be8eee"]]},{"id":"25ea02b9.be8eee","type":"delay","z":"74d8f714.ab8378","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":690,"y":1620,"wires":[["83bb0a3b.56c4d8"]]},{"id":"83bb0a3b.56c4d8","type":"change","z":"74d8f714.ab8378","name":"Last 10 events","rules":[{"t":"set","p":"topic","pt":"msg","to":"SELECT * FROM alarmlog ORDER BY timestamp DESC LIMIT 10","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":1620,"wires":[["927b5032.7c45e8"]]},{"id":"927b5032.7c45e8","type":"sqlite","z":"74d8f714.ab8378","mydb":"27e5ab9a.4c829c","sqlquery":"msg.topic","sql":"","name":"Alarm Log","x":1030,"y":1620,"wires":[["6c2561ae.43481","761d8c23.1ca984"]]},{"id":"761d8c23.1ca984","type":"template","z":"74d8f714.ab8378","name":"Format","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<table>\n    \n    <tr><th> Time </th><th> Channel </th><th> Alarm Message </th><th> Status </th><th>\n    {{#payload}}\n        <tr class=\"\">\n            <td><center>{{Time}}</td>\n            <td><center>{{Channel}}</td>\n            <td><center>{{Alarm}}</td>\n            <td><center>{{Status}}</td>\n        </tr>\n    {{/payload}}\n</table>\n","output":"str","x":1200,"y":1620,"wires":[["1cf08fea.b4b38"]]},{"id":"1cf08fea.b4b38","type":"ui_template","z":"74d8f714.ab8378","group":"783af07a.d61d58","name":"Alarm Log","order":4,"width":13,"height":8,"format":"<div ng-bind-html=\"msg.payload\" height=\"600\" style=\"height: 600px;\"></div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":1350,"y":1620,"wires":[[]]},{"id":"27d0e60a.18dc62","type":"inject","z":"74d8f714.ab8378","name":"create database","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"CREATE TABLE 'alarmlog' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'Time' TEXT, 'Channel' TEXT, 'Alarm' TEXT, 'Status' TEXT, 'timestamp' INTEGER DEFAULT CURRENT_TIMESTAMP)","payload":"","payloadType":"date","x":260,"y":1760,"wires":[["a896741a.d61798"]]},{"id":"a896741a.d61798","type":"sqlite","z":"74d8f714.ab8378","mydb":"27e5ab9a.4c829c","sqlquery":"msg.topic","sql":"","name":"Alarm Log","x":430,"y":1760,"wires":[["741330e7.ba891"]]},{"id":"741330e7.ba891","type":"debug","z":"74d8f714.ab8378","name":"","active":false,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":600,"y":1760,"wires":[]},{"id":"c6527b9f.17c448","type":"modbus-client","name":"","clienttype":"tcp","bufferCommands":false,"stateLogEnabled":false,"queueLogEnabled":false,"tcpHost":"192.168.1.120","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":1,"commandDelay":1,"clientTimeout":1000,"reconnectOnTimeout":true,"reconnectTimeout":2000,"parallelUnitIdsAllowed":true},{"id":"81dd3718.b97888","type":"ui_group","name":"Active Alarms","tab":"4e11db25.239e94","order":1,"disp":true,"width":13,"collapse":true},{"id":"27e5ab9a.4c829c","type":"sqlitedb","db":"nodered.db","mode":"RWC"},{"id":"783af07a.d61d58","type":"ui_group","name":"Alarm Log","tab":"4e11db25.239e94","order":4,"disp":false,"width":"13","collapse":false},{"id":"4e11db25.239e94","type":"ui_tab","name":"Alarms Monitiring","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

There are few things that I want to improve:

  • when the modebus connection is lost the Alarm List table is still showing the last alarms, better way is to create a dashboard element showing the connection state and to clear the table
  • the Alarm log HTML view, maybe to add some button to have a filter the log entries by 24hours, 1 week, 1 month.

Thanks for all your help.

Hello,
I have a question in regards with the Modbus read node.
As I mention before I want to pull 350 addresses from my PLC, but the reported array has just 94 values.
Screenshot_2021-03-26 Node-RED 172 16 10 12(1)
Screenshot_2021-03-26 Node-RED 172 16 10 12

It's something that I miss in configuration? how can I fix this?
Thanks in advance for your help.

Hi .. a similar question regarding the amount of registers you can read,
was asked on the developer's github page for the modbus node.

Possibly it could be a limitation on your device how many registers you can poll at once.

One workaround is to read them in chunks.

I do something similar reading from some Energy meters by chaining the nodes and then using a Join node to join the read values.

Funny thing - using a modbus slave emulator, if i request over 127 items, i start getting strange length responses in the debug output from node-red-contrib-modbus

The slave application seems to send all the data that i request. For example, I requested 150 items & the slave responded with the right amount of data...

but the modbus flex getter only shows me the first 22 items...
image

Another oddity is Wireshark doesn't correctly recognise the response...

Normal wireshark response (127 items)...

When i request 150 items...

And this online modbus packet parser doesnt like the response.


... which leads me to think ...

  • The response is actually NG or out of spec?
  • The wireshark filter and the web site checker tool is old / limited / broken?

I dont have the energy to scour the modbus spec so if anyone familiar with the modbus tcp specification wants to analyse the following request and response packets please do...

node-red --> slave

000100000006010300000096

slave --> node-red

00010000012f01032c00010002000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f0060000000000000000000000000000000000000000000000000000000000000007c007d007e007f00800081000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000


Possible work arounds...

  1. Do 4 sequential requests for 0~99 then 100~199 then 200~299 then 300~349 (you could do it in 3 reads but aligning to the 0, 100, 200 and 300 mark will make parsing simpler)
  2. DIY it - send 00010000000601030000015e over TCP to your slave & parse the result yourself

Demo DIY modbus TCP flow...
image

Results...

flow...

[{"id":"81661067.26948","type":"tcp request","z":"5e6c8b.7f38b374","server":"192.168.1.59","port":"502","out":"sit","splitc":" ","name":"","x":1908,"y":208,"wires":[["edb7a472.6eebc8"]]},{"id":"f77e01bf.42f16","type":"inject","z":"5e6c8b.7f38b374","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"00010000000601030000015e","payloadType":"str","x":1872,"y":144,"wires":[["50df09a0.9d44e8"]]},{"id":"50df09a0.9d44e8","type":"buffer-maker","z":"5e6c8b.7f38b374","name":"","specification":"spec","specificationType":"ui","items":[{"name":"item1","type":"hex","length":-1,"dataType":"msg","data":"payload"}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","x":2054,"y":144,"wires":[["cef58d63.4cb6","81661067.26948"]]},{"id":"f662fe41.9f3f3","type":"debug","z":"5e6c8b.7f38b374","name":"modbus response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2282,"y":208,"wires":[]},{"id":"cef58d63.4cb6","type":"debug","z":"5e6c8b.7f38b374","name":"modbus request","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2272,"y":144,"wires":[]},{"id":"edb7a472.6eebc8","type":"buffer-parser","z":"5e6c8b.7f38b374","name":"","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16be","name":"data","offset":9,"length":-1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"buffer","offset":9,"length":-1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":2102,"y":208,"wires":[["f662fe41.9f3f3"]]}]

NOTE: Here be dragons. The above flow does not check headers, does not do any error checking & i have no idea how it will handle disconnections. Feel free to use it or not but i would recommend at least checking the modbus headers and response data length at minimum before accepting the data as good

I'm unsure that is the real problem (see my post above).

some time ago, I was looking at nodejs modbus implementation and it had a bug whereby it would crash after a certain request size. I seem to remember something to do with it using a byte in a buffer to represent the request length (or the response length) so when a response > 255 bytes arrived, the "size" byte was rolled over (or the node crashed - i forget - it was quite some time ago))

I am no modbus specification expert and i really dont have the energy to debug it and check the spec but if you want to update your repo issue with the above info, feel free.

Slight update: Added header checks - works quite nicely reading 350 registers...

[{"id":"f77e01bf.42f16","type":"inject","z":"5e6c8b.7f38b374","name":"FC4: Read 50 from address 100","props":[{"p":"topic","vt":"str"},{"p":"sid","v":"1","vt":"num"},{"p":"pid","v":"0","vt":"num"},{"p":"len","v":"6","vt":"num"},{"p":"uid","v":"1","vt":"num"},{"p":"fc","v":"4","vt":"num"},{"p":"address","v":"100","vt":"num"},{"p":"registerCount","v":"50","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1602,"y":64,"wires":[["4e227fe.b16e08"]]},{"id":"f662fe41.9f3f3","type":"debug","z":"5e6c8b.7f38b374","name":"modbus response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":2282,"y":240,"wires":[]},{"id":"cef58d63.4cb6","type":"debug","z":"5e6c8b.7f38b374","name":"modbus request","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2272,"y":112,"wires":[]},{"id":"edb7a472.6eebc8","type":"buffer-parser","z":"5e6c8b.7f38b374","name":"Parse Response","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"int16be","name":"header=>sid","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"int16be","name":"header=>pid","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"int16be","name":"header=>len","offset":4,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"header=>uid","offset":6,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint8","name":"header=>fc","offset":7,"length":1,"offsetbit":0,"scale":"1","mask":"0x7F"},{"type":"uint8","name":"header=>byteCount","offset":8,"length":1,"offsetbit":0,"scale":"1","mask":"0x7F"},{"type":"uint16be","name":"data","offset":9,"length":-1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"buffer","offset":9,"length":-1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":2048,"y":176,"wires":[["79e4497c.3006e8"]]},{"id":"4e227fe.b16e08","type":"function","z":"5e6c8b.7f38b374","name":"inc SID","func":"var sid = context.get(\"sid\") || 0;\nsid++;\nif(sid > 250) sid = 0;\ncontext.set(\"sid\", sid) \nmsg.sid = sid;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1852,"y":112,"wires":[["a85ac5f2.9be538"]]},{"id":"a85ac5f2.9be538","type":"buffer-maker","z":"5e6c8b.7f38b374","name":"build MB Request","specification":"spec","specificationType":"ui","items":[{"name":"sid","type":"uint16be","length":1,"dataType":"msg","data":"sid"},{"name":"pid","type":"uint16be","length":1,"dataType":"msg","data":"pid"},{"name":"len","type":"uint16be","length":1,"dataType":"msg","data":"len"},{"name":"uid","type":"byte","length":1,"dataType":"msg","data":"uid"},{"name":"fc","type":"byte","length":1,"dataType":"msg","data":"fc"},{"name":"address","type":"uint16be","length":1,"dataType":"msg","data":"address"},{"name":"registerCount","type":"uint16be","length":1,"dataType":"msg","data":"registerCount"}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","x":2058,"y":112,"wires":[["cef58d63.4cb6","81661067.26948"]]},{"id":"79e4497c.3006e8","type":"function","z":"5e6c8b.7f38b374","name":"check response","func":"var header = msg.payload.header || {};\nvar sidOK = msg.sid == header.sid;\nvar pidOK = msg.pid == header.pid;\nvar uidOK = msg.uid == header.uid;\nvar fcOK = msg.fc == header.fc;\nvar countOK = msg.registerCount == (header.byteCount/2);\nif(!countOK && msg.registerCount > 127) {\n    countOK = msg.registerCount == msg.payload.data.length;\n}\n\nvar statText = \"\";\nif(!sidOK) {\n    statText = \"SID does not match. Sent: ${msg.sid}, Received: ${header.sid}\";\n    node.error(statText, msg);\n    node.status({text:statText});\n    return null;\n}\nif(!pidOK) {\n    statText = \"PID does not match. Sent: ${msg.pid}, Received: ${header.pid}\";\n    node.error(statText, msg);\n    node.status({text:statText});\n    return null;\n}\nif(!uidOK) {\n    statText = \"Unit ID does not match. Sent: ${msg.uid}, Received: ${header.uid}\";\n    node.error(statText, msg);\n    node.status({text:statText});\n    return null;\n}\nif(!fcOK) {\n    statText = \"FC does not match. Sent: ${msg.fc}, Received: ${header.fc}\";\n    node.error(statText, msg);\n    node.status({text:statText});\n    return null;\n}\nif(!countOK) {\n    statText = \"Response register count does not match requested count\";\n    node.error(statText, msg);\n    node.status({text:statText});\n    return null;\n}\nstatText = `OK. FC: ${msg.fc}, Addr: ${msg.address}[${msg.registerCount}], SID: (${msg.sid}) : Got ${msg.payload.data.length} register(s)` ;\nnode.status({text:statText});\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":2048,"y":240,"wires":[["f662fe41.9f3f3"]]},{"id":"d821be47.eb35a","type":"inject","z":"5e6c8b.7f38b374","name":"FC3: Read 350 from address 0","props":[{"p":"topic","vt":"str"},{"p":"sid","v":"1","vt":"num"},{"p":"pid","v":"0","vt":"num"},{"p":"len","v":"6","vt":"num"},{"p":"uid","v":"1","vt":"num"},{"p":"fc","v":"3","vt":"num"},{"p":"address","v":"0","vt":"num"},{"p":"registerCount","v":"350","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1602,"y":112,"wires":[["4e227fe.b16e08"]]},{"id":"81661067.26948","type":"tcp request","z":"5e6c8b.7f38b374","server":"192.168.1.59","port":"502","out":"sit","splitc":" ","name":"","x":1812,"y":176,"wires":[["edb7a472.6eebc8"]]}]

And you can now set the fc, address and registerCount in the inject.

LIMITATIONS: only fc3 and fc4 are handled (more work required to read coils or write to modbus - but it is possible - feel free to make a modbus (kinda) contrib out of it)

Thanks,
I'll look into the work around that was presented and see what's the easiest solution in my case.

Strange enough, if I'll pull request for 350 coils (FC1) I'm getting 352 values into the array.
I have to mention that for now I'm using a modbus simulator not the actual PLC.

Screenshot_2021-03-27 Node-RED 172 16 10 12(1)
Screenshot_2021-03-27 Node-RED 172 16 10 12

Which one? Can you share a link please?

That one is easily explained. The bools are probably packed in 8 or 16 bits. 352÷16=22 WORDS.

I find 2 simulators:

I'm using modrssim, for some reason modrssim2 doesn't work on my w10.

Hello,

I'm trying solve this by spiting the reading for the modbus addresses in 100's ... but I can't get settings to a merge array.
Can you please help,
thanks.

[{"id":"2d82b2f9.ef879e","type":"modbus-read","z":"74d8f714.ab8378","name":"","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"","dataType":"HoldingRegister","adr":"0","quantity":"99","rate":"5","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"2979eedb.de0842","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":250,"y":1460,"wires":[["5c7c274b.fce7f","115b2abd.6fd2fd"],[]]},{"id":"a993c182.d1aaf8","type":"modbus-read","z":"74d8f714.ab8378","name":"","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"","dataType":"HoldingRegister","adr":"100","quantity":"199","rate":"5","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"2979eedb.de0842","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":250,"y":1500,"wires":[["c5cd73e8.ab98a"],[]]},{"id":"c5cd73e8.ab98a","type":"function","z":"74d8f714.ab8378","name":"generate alarm table","func":"\nvar t = new Date();\nt = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + '/' + ('00' + (t.getMonth() + 1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());\n\nvar lookup = {\n    1: \"NO COMM. ON MAIN NETWORK\",\n    2: \"NO COMM. ON BACKUP NETWORK\",\n    3: \"MAIN SERVER OUT OF ORDER\",\n    4: \"BACKUP SERVER OUT OF ORDER\",\n    5: \"NO COMM. MAIN SERVER ON MAIN NETWORK\",\n    6: \"NO COMM. MAIN SERVER ON BACKUP NETWORK\",\n    7: \"NO COMM. BACKUP SERVER ON MAIN NETWORK\",\n    8: \"NO COMM. BACKUP SERVER ON BACKUP NETWORK\",\n    9: \"MAXIMUM DISKSPACE USED. LOGGING STOPPED\",\n    10: \"ALARMLOG: FILE CAN NOT BE WRITTEN\",\n    11: \"MTUS PORT: RD4 COM PORT FAILURE\",\n    12: \"MTUS PORT: RD4 NO COMMUNICATION\",\n    13: \"MTUS PORT: RD4 ERROR IN COMMUNICATION\",\n    14: \"MTUS STBD: RD5 COM PORT FAILURE\",\n    15: \"MTUS STBD: RD5 NO COMMUNICATION\",\n    16: \"MTUS STBD: RD5 ERROR IN COMMUNICATION\",\n    17: \"X101 NOT PRESENT\",\n    18: \"X102 NOT PRESENT\",\n    19: \"X103 NOT PRESENT\",\n    20: \"X104 NOT PRESENT\",\n    21: \"X109 NOT PRESENT\",\n    22: \"X110 NOT PRESENT\",\n    23: \"X111 NOT PRESENT\",\n    24: \"X117 NOT PRESENT\",\n    25: \"X118 NOT PRESENT\",\n    26: \"X119 NOT PRESENT\",\n    27: \"X125 NOT PRESENT\",\n    28: \"X126 NOT PRESENT\",\n    29: \"X127 NOT PRESENT\",\n    30: \"X128 NOT PRESENT\",\n    31: \"XP101 MAIN LINK NOT PRESENT\",\n    32: \"XP109 MAIN LINK NOT PRESENT\",\n    33: \"XP117 MAIN LINK NOT PRESENT\",\n    34: \"XP125 MAIN LINK NOT PRESENT\",\n    35: \"XP101 BACKUP LINK NOT PRESENT\",\n    36: \"XP109 BACKUP LINK NOT PRESENT\",\n    37: \"XP117 BACKUP LINK NOT PRESENT\",\n    38: \"XP125 BACKUP LINK NOT PRESENT\",\n    39: \"X101 INVALID BOARD TYPE\",\n    40: \"X102 INVALID BOARD TYPE\",\n    41: \"X103 INVALID BOARD TYPE\",\n    42: \"X104 INVALID BOARD TYPE\",\n    43: \"X109 INVALID BOARD TYPE\",\n    44: \"X110 INVALID BOARD TYPE\",\n    45: \"X111 INVALID BOARD TYPE\",\n    46: \"X117 INVALID BOARD TYPE\",\n    47: \"X118 INVALID BOARD TYPE\",\n    48: \"X119 INVALID BOARD TYPE\",\n    49: \"X125 INVALID BOARD TYPE\",\n    50: \"X126 INVALID BOARD TYPE\",\n    51: \"X127 INVALID BOARD TYPE\",\n    52: \"X128 INVALID BOARD TYPE\",\n    53: \"MBS_TCP: RD18 TCP/IP Socket Error\",\n    54: \"MBS_TCP: RD18 No Communication\",\n    55: \"MAIN SERVER IO SERVER NOT FOUND\",\n    56: \"BACKUP SERVER IO SERVER NOT FOUND\",\n    57: \"FB1 MAIN LINK NO COMMUNICATION\",\n    58: \"FB1 BACKUP LINK NO COMMUNICATION\",\n    59: \"FB2 MAIN LINK NO COMMUNICATION\",\n    60: \"X201 NOT PRESENT\",\n    61: \"X202 NOT PRESENT\",\n    62: \"X203 NOT PRESENT\",\n    63: \"X204 NOT PRESENT\",\n    64: \"X209 NOT PRESENT\",\n    65: \"X210 NOT PRESENT\",\n    66: \"X211 NOT PRESENT\",\n    67: \"X217 NOT PRESENT\",\n    68: \"X218 NOT PRESENT\",\n    69: \"X219 NOT PRESENT\",\n    70: \"X220 NOT PRESENT\",\n    71: \"FB2 BACKUP LINK NO COMMUNICATION\",\n    72: \"XP201 MAIN LINK NOT PRESENT\",\n    73: \"XP209 MAIN LINK NOT PRESENT\",\n    74: \"XP217 MAIN LINK NOT PRESENT\",\n    75: \"XP201 BACKUP LINK NOT PRESENT\",\n    76: \"XP209 BACKUP LINK NOT PRESENT\",\n    77: \"XP217 BACKUP LINK NOT PRESENT\",\n    78: \"X202 INVALID BOARD TYPE\",\n    79: \"X203 INVALID BOARD TYPE\",\n    80: \"X204 INVALID BOARD TYPE\",\n    81: \"X210 INVALID BOARD TYPE\",\n    82: \"X211 INVALID BOARD TYPE\",\n    83: \"X212 INVALID BOARD TYPE\",\n    84: \"X218 INVALID BOARD TYPE\",\n    85: \"X219 INVALID BOARD TYPE\",\n    86: \"X220 INVALID BOARD TYPE\",\n    87: \"X301 NOT PRESENT\",\n    88: \"X302 NOT PRESENT\",\n    89: \"X303 NOT PRESENT\",\n    90: \"X304 NOT PRESENT\",\n    91: \"X309 NOT PRESENT\",\n    92: \"XP301 MAIN LINK NOT PRESENT\",\n    93: \"XP309 MAIN LINK NOT PRESENT\",\n    94: \"XP301 BACKUP LINK NOT PRESENT\",\n    95: \"FB3 MAIN LINK NO COMMUNICATION\",\n    96: \"FB3 BACKUP LINK NO COMMUNICATION\",\n    97: \"FB3 PROC301 IEC-1131 CONFIG NOT LOADED\",\n    98: \"FB3 PROC301 IEC-1131 PROGRAM NOT RUNNING\",\n    99: \"NMEA_WIND: RD6 COM PORT ERROR\",\n    100: \"NMEA_WIND: RD6 NO COMMUNICATION\",\n    101: \"FB3 PROC309 IEC-1131 CONFIG NOT LOADED\",\n    102: \"FB3 PROC309 IEC-1131 PROGRAM NOT RUNNING\",\n    103: \"X301 INVALID BOARD TYPE\",\n    104: \"X302 INVALID BOARD TYPE\",\n    105: \"X303 INVALID BOARD TYPE\",\n    106: \"X304 INVALID BOARD TYPE\",\n    107: \"X309 INVALID BOARD TYPE\",\n    108: \"FB4 MAIN LINK NO COMMUNICATION\",\n    109: \"FB4 BACKUP LINK NO COMMUNICATION\", \n    110: \"FB4 LOP MESS NOT PRESENT\",\n    111: \"FB4 LOP ENGINEER NOT PRESENT\",\n    112: \"FB4 LOP 1ST MATE NOT PRESENT\",\n    113: \"FB4 LOP ALARMPANEL ENGINEROOM\",\n    114: \"ALARM PANEL E.R. POWER FAIL\",\n    115: \"ALARM PANEL E.R. EARTH FAULT\",\n    116: \"WATERTIGHT DOORS POWER FAILURE\",\n    117: \"WATERTIGHT DOORS BATTERY LOW\",\n    118: \"BILGE FORE PEAK LEVEL HIGH\",\n    119: \"BILGE BOW THRUSTER ROOM LEVEL HIGH\",\n    120: \"BILGE COFFERDAM LEVEL HIGH\"\n}\n\n\n\nvar previous = context.get(\"previous\") || [];\nvar current = [...msg.payload];\nvar table = context.get(\"table\") || [];\nfor (var i = 0; i < msg.payload.length; i++) {\n    var channel = (\"\" + (i + 100)).padStart(5,\"0\");\n    var sensorNo = (i + 100);\n    var sensorName = lookup[sensorNo] || (\"Unknown \" + sensorNo);\n    //has value changed?\n    if (previous[i] !== current[i]) {\n        var row = table.find(e => e.Channel == channel);\n\n        if (!row) {\n            if (current[i] === 0)\n                continue; //dont add this one (its 0)\n            \n            row = {\n                \"Time\":t,\n                \"Channel\": channel,\n                \"Alarm\": \"\",                \n                \"Status\": \"\",\n                \"Alarmlog\": \"\",                \n                \"Statuslog\": \"\"                \n            }            \n            table.push(row);\n        } else {\n            //there is a row in the table...\n            if (current[i] === 0) {\n                ///remove it because its now 0\n                table = table.filter(e => e.Channel !== channel);\n                continue;\n            }\n        }\n\n        row.Time = t;//update the time\n        if (current[i] === 2) {\n            row.Status = \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM !\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1027) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1028) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1029) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1088) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"SENSOR FAIL\";\n            row.Alarmlog = sensorName ;\n        }\n                //TODO: \n        // You can create the SQL here and send it in the topic OR send an object with \n        // the clean alarm and notes info (i.e. no HTML) then build a SQL query in the next node \n        node.send( [null, { topic: (\"INSERT INTO alarmlog (Time,Channel,Alarm,Status) \" +\n        \"VALUES ('\"+row.Time+\"','\"+row.Channel+\"','\"+row.Alarmlog+\"','\"+row.Statuslog+\"')\")}] ); //Send the SQL query out of output 2\n    }\n}\n\ncontext.set(\"previous\", current);\ncontext.set(\"table\", table);\n\nmsg.payload = table;\nreturn [msg, null]; //Return table to function output 1;","outputs":2,"noerr":0,"initialize":"","finalize":"","x":460,"y":1500,"wires":[["b59febd3.ec7848","be345f3e.1a55f"],["84fb433e.b0a2f8"]]},{"id":"115b2abd.6fd2fd","type":"function","z":"74d8f714.ab8378","name":"generate alarm table","func":"\nvar t = new Date();\nt = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + '/' + ('00' + (t.getMonth() + 1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());\n\nvar lookup = {\n    1: \"NO COMM. ON MAIN NETWORK\",\n    2: \"NO COMM. ON BACKUP NETWORK\",\n    3: \"MAIN SERVER OUT OF ORDER\",\n    4: \"BACKUP SERVER OUT OF ORDER\",\n    5: \"NO COMM. MAIN SERVER ON MAIN NETWORK\",\n    6: \"NO COMM. MAIN SERVER ON BACKUP NETWORK\",\n    7: \"NO COMM. BACKUP SERVER ON MAIN NETWORK\",\n    8: \"NO COMM. BACKUP SERVER ON BACKUP NETWORK\",\n    9: \"MAXIMUM DISKSPACE USED. LOGGING STOPPED\",\n    10: \"ALARMLOG: FILE CAN NOT BE WRITTEN\",\n    11: \"MTUS PORT: RD4 COM PORT FAILURE\",\n    12: \"MTUS PORT: RD4 NO COMMUNICATION\",\n    13: \"MTUS PORT: RD4 ERROR IN COMMUNICATION\",\n    14: \"MTUS STBD: RD5 COM PORT FAILURE\",\n    15: \"MTUS STBD: RD5 NO COMMUNICATION\",\n    16: \"MTUS STBD: RD5 ERROR IN COMMUNICATION\",\n    17: \"X101 NOT PRESENT\",\n    18: \"X102 NOT PRESENT\",\n    19: \"X103 NOT PRESENT\",\n    20: \"X104 NOT PRESENT\",\n    21: \"X109 NOT PRESENT\",\n    22: \"X110 NOT PRESENT\",\n    23: \"X111 NOT PRESENT\",\n    24: \"X117 NOT PRESENT\",\n    25: \"X118 NOT PRESENT\",\n    26: \"X119 NOT PRESENT\",\n    27: \"X125 NOT PRESENT\",\n    28: \"X126 NOT PRESENT\",\n    29: \"X127 NOT PRESENT\",\n    30: \"X128 NOT PRESENT\",\n    31: \"XP101 MAIN LINK NOT PRESENT\",\n    32: \"XP109 MAIN LINK NOT PRESENT\",\n    33: \"XP117 MAIN LINK NOT PRESENT\",\n    34: \"XP125 MAIN LINK NOT PRESENT\",\n    35: \"XP101 BACKUP LINK NOT PRESENT\",\n    36: \"XP109 BACKUP LINK NOT PRESENT\",\n    37: \"XP117 BACKUP LINK NOT PRESENT\",\n    38: \"XP125 BACKUP LINK NOT PRESENT\",\n    39: \"X101 INVALID BOARD TYPE\",\n    40: \"X102 INVALID BOARD TYPE\",\n    41: \"X103 INVALID BOARD TYPE\",\n    42: \"X104 INVALID BOARD TYPE\",\n    43: \"X109 INVALID BOARD TYPE\",\n    44: \"X110 INVALID BOARD TYPE\",\n    45: \"X111 INVALID BOARD TYPE\",\n    46: \"X117 INVALID BOARD TYPE\",\n    47: \"X118 INVALID BOARD TYPE\",\n    48: \"X119 INVALID BOARD TYPE\",\n    49: \"X125 INVALID BOARD TYPE\",\n    50: \"X126 INVALID BOARD TYPE\",\n    51: \"X127 INVALID BOARD TYPE\",\n    52: \"X128 INVALID BOARD TYPE\",\n    53: \"MBS_TCP: RD18 TCP/IP Socket Error\",\n    54: \"MBS_TCP: RD18 No Communication\",\n    55: \"MAIN SERVER IO SERVER NOT FOUND\",\n    56: \"BACKUP SERVER IO SERVER NOT FOUND\",\n    57: \"FB1 MAIN LINK NO COMMUNICATION\",\n    58: \"FB1 BACKUP LINK NO COMMUNICATION\",\n    59: \"FB2 MAIN LINK NO COMMUNICATION\",\n    60: \"X201 NOT PRESENT\",\n    61: \"X202 NOT PRESENT\",\n    62: \"X203 NOT PRESENT\",\n    63: \"X204 NOT PRESENT\",\n    64: \"X209 NOT PRESENT\",\n    65: \"X210 NOT PRESENT\",\n    66: \"X211 NOT PRESENT\",\n    67: \"X217 NOT PRESENT\",\n    68: \"X218 NOT PRESENT\",\n    69: \"X219 NOT PRESENT\",\n    70: \"X220 NOT PRESENT\",\n    71: \"FB2 BACKUP LINK NO COMMUNICATION\",\n    72: \"XP201 MAIN LINK NOT PRESENT\",\n    73: \"XP209 MAIN LINK NOT PRESENT\",\n    74: \"XP217 MAIN LINK NOT PRESENT\",\n    75: \"XP201 BACKUP LINK NOT PRESENT\",\n    76: \"XP209 BACKUP LINK NOT PRESENT\",\n    77: \"XP217 BACKUP LINK NOT PRESENT\",\n    78: \"X202 INVALID BOARD TYPE\",\n    79: \"X203 INVALID BOARD TYPE\",\n    80: \"X204 INVALID BOARD TYPE\",\n    81: \"X210 INVALID BOARD TYPE\",\n    82: \"X211 INVALID BOARD TYPE\",\n    83: \"X212 INVALID BOARD TYPE\",\n    84: \"X218 INVALID BOARD TYPE\",\n    85: \"X219 INVALID BOARD TYPE\",\n    86: \"X220 INVALID BOARD TYPE\",\n    87: \"X301 NOT PRESENT\",\n    88: \"X302 NOT PRESENT\",\n    89: \"X303 NOT PRESENT\",\n    90: \"X304 NOT PRESENT\",\n    91: \"X309 NOT PRESENT\",\n    92: \"XP301 MAIN LINK NOT PRESENT\",\n    93: \"XP309 MAIN LINK NOT PRESENT\",\n    94: \"XP301 BACKUP LINK NOT PRESENT\",\n    95: \"FB3 MAIN LINK NO COMMUNICATION\",\n    96: \"FB3 BACKUP LINK NO COMMUNICATION\",\n    97: \"FB3 PROC301 IEC-1131 CONFIG NOT LOADED\",\n    98: \"FB3 PROC301 IEC-1131 PROGRAM NOT RUNNING\",\n    99: \"NMEA_WIND: RD6 COM PORT ERROR\",\n    100: \"NMEA_WIND: RD6 NO COMMUNICATION\",\n    101: \"FB3 PROC309 IEC-1131 CONFIG NOT LOADED\",\n    102: \"FB3 PROC309 IEC-1131 PROGRAM NOT RUNNING\",\n    103: \"X301 INVALID BOARD TYPE\",\n    104: \"X302 INVALID BOARD TYPE\",\n    105: \"X303 INVALID BOARD TYPE\",\n    106: \"X304 INVALID BOARD TYPE\",\n    107: \"X309 INVALID BOARD TYPE\",\n    108: \"FB4 MAIN LINK NO COMMUNICATION\",\n    109: \"FB4 BACKUP LINK NO COMMUNICATION\", \n    110: \"FB4 LOP MESS NOT PRESENT\",\n    111: \"FB4 LOP ENGINEER NOT PRESENT\",\n    112: \"FB4 LOP 1ST MATE NOT PRESENT\",\n    113: \"FB4 LOP ALARMPANEL ENGINEROOM\",\n    114: \"ALARM PANEL E.R. POWER FAIL\",\n    115: \"ALARM PANEL E.R. EARTH FAULT\",\n    116: \"WATERTIGHT DOORS POWER FAILURE\",\n    117: \"WATERTIGHT DOORS BATTERY LOW\",\n    118: \"BILGE FORE PEAK LEVEL HIGH\",\n    119: \"BILGE BOW THRUSTER ROOM LEVEL HIGH\",\n    120: \"BILGE COFFERDAM LEVEL HIGH\"\n}\n\n\n\nvar previous = context.get(\"previous\") || [];\nvar current = [...msg.payload];\nvar table = context.get(\"table\") || [];\nfor (var i = 0; i < msg.payload.length; i++) {\n    var channel = (\"\" + (i + 1)).padStart(5,\"0\");\n    var sensorNo = (i + 1);\n    var sensorName = lookup[sensorNo] || (\"Unknown \" + sensorNo);\n    //has value changed?\n    if (previous[i] !== current[i]) {\n        var row = table.find(e => e.Channel == channel);\n\n        if (!row) {\n            if (current[i] === 0)\n                continue; //dont add this one (its 0)\n            \n            row = {\n                \"Time\":t,\n                \"Channel\": channel,\n                \"Alarm\": \"\",                \n                \"Status\": \"\",\n                \"Alarmlog\": \"\",                \n                \"Statuslog\": \"\"                \n            }            \n            table.push(row);\n        } else {\n            //there is a row in the table...\n            if (current[i] === 0) {\n                ///remove it because its now 0\n                table = table.filter(e => e.Channel !== channel);\n                continue;\n            }\n        }\n\n        row.Time = t;//update the time\n        if (current[i] === 2) {\n            row.Status = \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM !\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1027) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1028) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1029) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1088) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"SENSOR FAIL\";\n            row.Alarmlog = sensorName ;\n        }\n                //TODO: \n        // You can create the SQL here and send it in the topic OR send an object with \n        // the clean alarm and notes info (i.e. no HTML) then build a SQL query in the next node \n        node.send( [null, { topic: (\"INSERT INTO alarmlog (Time,Channel,Alarm,Status) \" +\n        \"VALUES ('\"+row.Time+\"','\"+row.Channel+\"','\"+row.Alarmlog+\"','\"+row.Statuslog+\"')\")}] ); //Send the SQL query out of output 2\n    }\n}\n\ncontext.set(\"previous\", current);\ncontext.set(\"table\", table);\n\nmsg.payload = table;\nreturn [msg, null]; //Return table to function output 1;","outputs":2,"noerr":0,"initialize":"","finalize":"","x":460,"y":1460,"wires":[["1caa8632.9ffda2","be345f3e.1a55f"],["33eac830.bf165","25ea02b9.be8eee"]]},{"id":"be345f3e.1a55f","type":"join","z":"74d8f714.ab8378","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"4","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":680,"y":1500,"wires":[["e36f3a9f.a38dd8"]]},{"id":"e36f3a9f.a38dd8","type":"ui_table","z":"74d8f714.ab8378","group":"81dd3718.b97888","name":"Alarm List","order":1,"width":13,"height":8,"columns":[{"field":"Time","title":"<center>Time</>","width":"23%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Channel","title":"<center>Channel</>","width":"10%","align":"center","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"Alarm","title":"<center>Alarm Message</>","width":"52%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}},{"field":"Status","title":"<center>Status</>","width":"15%","align":"center","formatter":"html","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":900,"y":1440,"wires":[]},{"id":"2979eedb.de0842","type":"modbus-client","name":"","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"tcpHost":"172.16.10.71","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":"1","commandDelay":"1","clientTimeout":"1000","reconnectOnTimeout":true,"reconnectTimeout":"2000","parallelUnitIdsAllowed":true},{"id":"81dd3718.b97888","type":"ui_group","name":"Active Alarms","tab":"4e11db25.239e94","order":1,"disp":true,"width":13,"collapse":true},{"id":"4e11db25.239e94","type":"ui_tab","name":"Alarms Monitiring","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Try this...

the key to this is to fire the reads sequentially. (PS, you will need to chose your modbus config in the modbus nodes)

[{"id":"e65eb5ea.4fa858","type":"modbus-getter","z":"5e6c8b.7f38b374","name":"0 ~ 99","showStatusActivities":false,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"0","quantity":"100","server":"d1841b37.604628","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":false,"x":1584,"y":960,"wires":[["63c6d18f.950c8"],[]]},{"id":"2cc804d5.2e33bc","type":"modbus-getter","z":"5e6c8b.7f38b374","name":"100 ~ 199","showStatusActivities":false,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"100","quantity":"100","server":"d1841b37.604628","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":true,"x":1596,"y":1024,"wires":[["9a94f150.5222"],[]]},{"id":"63c6d18f.950c8","type":"change","z":"5e6c8b.7f38b374","name":"","rules":[{"t":"set","p":"data1","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1776,"y":960,"wires":[["4fb126bd.00d5f8","2cc804d5.2e33bc"]]},{"id":"85684a01.bfd1f8","type":"inject","z":"5e6c8b.7f38b374","name":"","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":1442,"y":960,"wires":[["e65eb5ea.4fa858"]]},{"id":"9a94f150.5222","type":"change","z":"5e6c8b.7f38b374","name":"","rules":[{"t":"set","p":"data2","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1776,"y":1024,"wires":[["196ffb.89463005","ffc09dbd.9bc89"]]},{"id":"196ffb.89463005","type":"modbus-getter","z":"5e6c8b.7f38b374","name":"200 ~ 299","showStatusActivities":false,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"100","quantity":"100","server":"d1841b37.604628","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":true,"x":1596,"y":1088,"wires":[["72d2709e.14a7d"],[]]},{"id":"72d2709e.14a7d","type":"change","z":"5e6c8b.7f38b374","name":"","rules":[{"t":"set","p":"data3","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1776,"y":1088,"wires":[["d5a57cd.c815d8","c18e20a2.23a71"]]},{"id":"d5a57cd.c815d8","type":"function","z":"5e6c8b.7f38b374","name":"Make 1 array","func":"msg.payload = [msg.data1.map(e => e), msg.data2.map(e => e), msg.data3.map(e => e)]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1686,"y":1168,"wires":[["cf84ffc3.151ed","58fd7ace.0eb404"]]},{"id":"cf84ffc3.151ed","type":"function","z":"5e6c8b.7f38b374","name":"generate alarm table","func":"\nvar t = new Date();\nt = (('00' + t.getHours()).slice(-2) + ':' + ('00' + t.getMinutes()).slice(-2) + ':' + ('00' + t.getSeconds()).slice(-2) + '/' + ('00' + (t.getMonth() + 1)).slice(-2) + '.' + ('00' + t.getDate()).slice(-2) + '.' + t.getFullYear());\n\nvar lookup = {\n    1: \"NO COMM. ON MAIN NETWORK\",\n    2: \"NO COMM. ON BACKUP NETWORK\",\n    3: \"MAIN SERVER OUT OF ORDER\",\n    4: \"BACKUP SERVER OUT OF ORDER\",\n    5: \"NO COMM. MAIN SERVER ON MAIN NETWORK\",\n    6: \"NO COMM. MAIN SERVER ON BACKUP NETWORK\",\n    7: \"NO COMM. BACKUP SERVER ON MAIN NETWORK\",\n    8: \"NO COMM. BACKUP SERVER ON BACKUP NETWORK\",\n    9: \"MAXIMUM DISKSPACE USED. LOGGING STOPPED\",\n    10: \"ALARMLOG: FILE CAN NOT BE WRITTEN\",\n    11: \"MTUS PORT: RD4 COM PORT FAILURE\",\n    12: \"MTUS PORT: RD4 NO COMMUNICATION\",\n    13: \"MTUS PORT: RD4 ERROR IN COMMUNICATION\",\n    14: \"MTUS STBD: RD5 COM PORT FAILURE\",\n    15: \"MTUS STBD: RD5 NO COMMUNICATION\",\n    16: \"MTUS STBD: RD5 ERROR IN COMMUNICATION\",\n    17: \"X101 NOT PRESENT\",\n    18: \"X102 NOT PRESENT\",\n    19: \"X103 NOT PRESENT\",\n    20: \"X104 NOT PRESENT\",\n    21: \"X109 NOT PRESENT\",\n    22: \"X110 NOT PRESENT\",\n    23: \"X111 NOT PRESENT\",\n    24: \"X117 NOT PRESENT\",\n    25: \"X118 NOT PRESENT\",\n    26: \"X119 NOT PRESENT\",\n    27: \"X125 NOT PRESENT\",\n    28: \"X126 NOT PRESENT\",\n    29: \"X127 NOT PRESENT\",\n    30: \"X128 NOT PRESENT\",\n    31: \"XP101 MAIN LINK NOT PRESENT\",\n    32: \"XP109 MAIN LINK NOT PRESENT\",\n    33: \"XP117 MAIN LINK NOT PRESENT\",\n    34: \"XP125 MAIN LINK NOT PRESENT\",\n    35: \"XP101 BACKUP LINK NOT PRESENT\",\n    36: \"XP109 BACKUP LINK NOT PRESENT\",\n    37: \"XP117 BACKUP LINK NOT PRESENT\",\n    38: \"XP125 BACKUP LINK NOT PRESENT\",\n    39: \"X101 INVALID BOARD TYPE\",\n    40: \"X102 INVALID BOARD TYPE\",\n    41: \"X103 INVALID BOARD TYPE\",\n    42: \"X104 INVALID BOARD TYPE\",\n    43: \"X109 INVALID BOARD TYPE\",\n    44: \"X110 INVALID BOARD TYPE\",\n    45: \"X111 INVALID BOARD TYPE\",\n    46: \"X117 INVALID BOARD TYPE\",\n    47: \"X118 INVALID BOARD TYPE\",\n    48: \"X119 INVALID BOARD TYPE\",\n    49: \"X125 INVALID BOARD TYPE\",\n    50: \"X126 INVALID BOARD TYPE\",\n    51: \"X127 INVALID BOARD TYPE\",\n    52: \"X128 INVALID BOARD TYPE\",\n    53: \"MBS_TCP: RD18 TCP/IP Socket Error\",\n    54: \"MBS_TCP: RD18 No Communication\",\n    55: \"MAIN SERVER IO SERVER NOT FOUND\",\n    56: \"BACKUP SERVER IO SERVER NOT FOUND\",\n    57: \"FB1 MAIN LINK NO COMMUNICATION\",\n    58: \"FB1 BACKUP LINK NO COMMUNICATION\",\n    59: \"FB2 MAIN LINK NO COMMUNICATION\",\n    60: \"X201 NOT PRESENT\",\n    61: \"X202 NOT PRESENT\",\n    62: \"X203 NOT PRESENT\",\n    63: \"X204 NOT PRESENT\",\n    64: \"X209 NOT PRESENT\",\n    65: \"X210 NOT PRESENT\",\n    66: \"X211 NOT PRESENT\",\n    67: \"X217 NOT PRESENT\",\n    68: \"X218 NOT PRESENT\",\n    69: \"X219 NOT PRESENT\",\n    70: \"X220 NOT PRESENT\",\n    71: \"FB2 BACKUP LINK NO COMMUNICATION\",\n    72: \"XP201 MAIN LINK NOT PRESENT\",\n    73: \"XP209 MAIN LINK NOT PRESENT\",\n    74: \"XP217 MAIN LINK NOT PRESENT\",\n    75: \"XP201 BACKUP LINK NOT PRESENT\",\n    76: \"XP209 BACKUP LINK NOT PRESENT\",\n    77: \"XP217 BACKUP LINK NOT PRESENT\",\n    78: \"X202 INVALID BOARD TYPE\",\n    79: \"X203 INVALID BOARD TYPE\",\n    80: \"X204 INVALID BOARD TYPE\",\n    81: \"X210 INVALID BOARD TYPE\",\n    82: \"X211 INVALID BOARD TYPE\",\n    83: \"X212 INVALID BOARD TYPE\",\n    84: \"X218 INVALID BOARD TYPE\",\n    85: \"X219 INVALID BOARD TYPE\",\n    86: \"X220 INVALID BOARD TYPE\",\n    87: \"X301 NOT PRESENT\",\n    88: \"X302 NOT PRESENT\",\n    89: \"X303 NOT PRESENT\",\n    90: \"X304 NOT PRESENT\",\n    91: \"X309 NOT PRESENT\",\n    92: \"XP301 MAIN LINK NOT PRESENT\",\n    93: \"XP309 MAIN LINK NOT PRESENT\",\n    94: \"XP301 BACKUP LINK NOT PRESENT\",\n    95: \"FB3 MAIN LINK NO COMMUNICATION\",\n    96: \"FB3 BACKUP LINK NO COMMUNICATION\",\n    97: \"FB3 PROC301 IEC-1131 CONFIG NOT LOADED\",\n    98: \"FB3 PROC301 IEC-1131 PROGRAM NOT RUNNING\",\n    99: \"NMEA_WIND: RD6 COM PORT ERROR\",\n    100: \"NMEA_WIND: RD6 NO COMMUNICATION\",\n    101: \"FB3 PROC309 IEC-1131 CONFIG NOT LOADED\",\n    102: \"FB3 PROC309 IEC-1131 PROGRAM NOT RUNNING\",\n    103: \"X301 INVALID BOARD TYPE\",\n    104: \"X302 INVALID BOARD TYPE\",\n    105: \"X303 INVALID BOARD TYPE\",\n    106: \"X304 INVALID BOARD TYPE\",\n    107: \"X309 INVALID BOARD TYPE\",\n    108: \"FB4 MAIN LINK NO COMMUNICATION\",\n    109: \"FB4 BACKUP LINK NO COMMUNICATION\", \n    110: \"FB4 LOP MESS NOT PRESENT\",\n    111: \"FB4 LOP ENGINEER NOT PRESENT\",\n    112: \"FB4 LOP 1ST MATE NOT PRESENT\",\n    113: \"FB4 LOP ALARMPANEL ENGINEROOM\",\n    114: \"ALARM PANEL E.R. POWER FAIL\",\n    115: \"ALARM PANEL E.R. EARTH FAULT\",\n    116: \"WATERTIGHT DOORS POWER FAILURE\",\n    117: \"WATERTIGHT DOORS BATTERY LOW\",\n    118: \"BILGE FORE PEAK LEVEL HIGH\",\n    119: \"BILGE BOW THRUSTER ROOM LEVEL HIGH\",\n    120: \"BILGE COFFERDAM LEVEL HIGH\"\n}\n\n\n\nvar previous = context.get(\"previous\") || [];\nvar current = [...msg.payload];\nvar table = context.get(\"table\") || [];\nfor (var i = 0; i < msg.payload.length; i++) {\n    var channel = (\"\" + (i + 100)).padStart(5,\"0\");\n    var sensorNo = (i + 100);\n    var sensorName = lookup[sensorNo] || (\"Unknown \" + sensorNo);\n    //has value changed?\n    if (previous[i] !== current[i]) {\n        var row = table.find(e => e.Channel == channel);\n\n        if (!row) {\n            if (current[i] === 0)\n                continue; //dont add this one (its 0)\n            \n            row = {\n                \"Time\":t,\n                \"Channel\": channel,\n                \"Alarm\": \"\",                \n                \"Status\": \"\",\n                \"Alarmlog\": \"\",                \n                \"Statuslog\": \"\"                \n            }            \n            table.push(row);\n        } else {\n            //there is a row in the table...\n            if (current[i] === 0) {\n                ///remove it because its now 0\n                table = table.filter(e => e.Channel !== channel);\n                continue;\n            }\n        }\n\n        row.Time = t;//update the time\n        if (current[i] === 2) {\n            row.Status = \"<div style='background-color: red; width:100%; color:white'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM !\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1027) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;            \n        } else if (current[i] === 1028) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1029) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"ALARM\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"ALARM\";\n            row.Alarmlog = sensorName ;\n        } else if (current[i] === 1088) {\n            row.Status = \"<div style='background-color: yellow; width:100%; color:red'>\" + \"SENSOR FAIL\" + \"</div>\";\n            row.Alarm = \"<div style='background-color: red; width:100%; color:white'>\" + sensorName + \"</div>\";\n            row.Statuslog = \"SENSOR FAIL\";\n            row.Alarmlog = sensorName ;\n        }\n                //TODO: \n        // You can create the SQL here and send it in the topic OR send an object with \n        // the clean alarm and notes info (i.e. no HTML) then build a SQL query in the next node \n        node.send( [null, { topic: (\"INSERT INTO alarmlog (Time,Channel,Alarm,Status) \" +\n        \"VALUES ('\"+row.Time+\"','\"+row.Channel+\"','\"+row.Alarmlog+\"','\"+row.Statuslog+\"')\")}] ); //Send the SQL query out of output 2\n    }\n}\n\ncontext.set(\"previous\", current);\ncontext.set(\"table\", table);\n\nmsg.payload = table;\nreturn [msg, null]; //Return table to function output 1;","outputs":2,"noerr":0,"initialize":"","finalize":"","x":1924,"y":1168,"wires":[["37fac07c.eb383"],["b3b05181.6d535"]]},{"id":"37fac07c.eb383","type":"debug","z":"5e6c8b.7f38b374","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2226,"y":1136,"wires":[]},{"id":"b3b05181.6d535","type":"debug","z":"5e6c8b.7f38b374","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2226,"y":1184,"wires":[]},{"id":"4fb126bd.00d5f8","type":"debug","z":"5e6c8b.7f38b374","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2002,"y":960,"wires":[]},{"id":"ffc09dbd.9bc89","type":"debug","z":"5e6c8b.7f38b374","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2002,"y":1024,"wires":[]},{"id":"58fd7ace.0eb404","type":"debug","z":"5e6c8b.7f38b374","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1698,"y":1216,"wires":[]},{"id":"c18e20a2.23a71","type":"debug","z":"5e6c8b.7f38b374","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2002,"y":1088,"wires":[]},{"id":"d1841b37.604628","type":"modbus-client","name":"","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"tcpHost":"192.168.1.59","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":"1","commandDelay":"1","clientTimeout":"1000","reconnectOnTimeout":true,"reconnectTimeout":"2000","parallelUnitIdsAllowed":true}]

NOTE: Why yours didnt work...

  1. You have no guarantee which of those read nodes would fire first (then the join would make the 1st array 2nd and the 2nd array first - completely out of order)
  2. There can be problems sending reads to a modbus at the same time. Reading sequentially avoids this potential issue.
  3. You were duplicating the function node which was designed to operate on a single array (of any size) - the solution was to join the arrays BEFORE sending to the function (not the other way around)