How to create an Modbus-TCP alarm status dashboard

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)

hehe .. and then Steve proceeds to analyse the packets with wireshark and writes his own implementation of modbus requests / replies :wink:

yea .. possibly the modbus node was a little buggy with the reply length.
judging from his replies on github its a pitty the developer doesnt have much free time to develop it further.

I read a bit on the modbus specifications and apparently there is a limit of 256 bytes for the reply.
One interesting post i found was of a person making some calculations taking into account the bytes needed for the headers and deducts that the max number of registers that can be read in a single Modbus/RTU query is 125 and 123 for Modbus TCP. ARTICLE

2 Likes

Based on your suggestions, here is the flow:

It can be extended to as many addresses needed to be read.
In function node I mage the following change:

from

var current = [...msg.payload];
for (var i = 0; i < msg.payload.length; i++) { ...

to

var current = [...msg.data1,...msg.data2,...msg.data3];
for (var i = 0; i < current.length; i++) {....

thanks for your help

[{"id":"5860dff5.9728c8","type":"modbus-getter","z":"1ac2afc6.199a18","name":"0 ~ 99","showStatusActivities":true,"showErrors":true,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"0","quantity":"100","server":"2979eedb.de0842","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":true,"keepMsgProperties":false,"x":330,"y":960,"wires":[["68d2243e.eca644"],[]]},{"id":"7e92c260.6f4cc4","type":"modbus-getter","z":"1ac2afc6.199a18","name":"100 ~ 199","showStatusActivities":false,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"100","quantity":"100","server":"2979eedb.de0842","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":true,"x":330,"y":1020,"wires":[["9ffd9b1c.94276"],[]]},{"id":"1c8d5b97.10df34","type":"inject","z":"1ac2afc6.199a18","name":"5 sec","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":190,"y":960,"wires":[["5860dff5.9728c8"]]},{"id":"afcec612.c1d05","type":"modbus-getter","z":"1ac2afc6.199a18","name":"200 ~ 299","showStatusActivities":false,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"200","quantity":"100","server":"2979eedb.de0842","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":true,"x":330,"y":1080,"wires":[["5aaf8adf.7150f4"],[]]},{"id":"68d2243e.eca644","type":"change","z":"1ac2afc6.199a18","name":"","rules":[{"t":"set","p":"data1","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":960,"wires":[["7e92c260.6f4cc4"]]},{"id":"9ffd9b1c.94276","type":"change","z":"1ac2afc6.199a18","name":"","rules":[{"t":"set","p":"data2","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":1020,"wires":[["afcec612.c1d05"]]},{"id":"5aaf8adf.7150f4","type":"change","z":"1ac2afc6.199a18","name":"","rules":[{"t":"set","p":"data3","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":1080,"wires":[["772be6c0.996ca"]]},{"id":"772be6c0.996ca","type":"buffer-array","z":"1ac2afc6.199a18","name":"","bufferLen":"3","startWhenFilled":true,"x":490,"y":1140,"wires":[["9f1d845b.532d8"]]},{"id":"9f1d845b.532d8","type":"debug","z":"1ac2afc6.199a18","name":"Modbus Read","active":false,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":670,"y":1140,"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}]

I want to display on the dashboard the modebus connection status.

How can I extract the msg :error that is display by debugging node?
I try to use the status node, I'm getting the node status but not the error message.

Screenshot_2021-03-28 Node-RED 172 16 10 226(1)

Screenshot_2021-03-28 Node-RED 172 16 10 226(2)

Thanks.

Hi .. the way I handled Modbus errors from these nodes was to enable in their configuration the option to output an Empty msg on fail.

image

After that i wired a function node on each of the reads to check whether the msg was an error or valid modbus read and route it to the appropriate function output.
( top output if it was an error, bottom output if it was valid )

Example flow :

[{"id":"5860dff5.9728c8","type":"modbus-getter","z":"c7c6d4d7.58fc3","name":"0 ~ 99","showStatusActivities":true,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"0","quantity":"100","server":"2979eedb.de0842","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":true,"keepMsgProperties":true,"x":310,"y":180,"wires":[["50f4d5f8.e58de4"],[]]},{"id":"7e92c260.6f4cc4","type":"modbus-getter","z":"c7c6d4d7.58fc3","name":"100 ~ 199","showStatusActivities":true,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"100","quantity":"100","server":"","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":true,"keepMsgProperties":true,"x":290,"y":300,"wires":[["7530ea64.384ca4"],[]]},{"id":"1c8d5b97.10df34","type":"inject","z":"c7c6d4d7.58fc3","name":"5 sec","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":170,"y":180,"wires":[["5860dff5.9728c8"]]},{"id":"afcec612.c1d05","type":"modbus-getter","z":"c7c6d4d7.58fc3","name":"200 ~ 299","showStatusActivities":true,"showErrors":false,"logIOActivities":false,"unitid":"1","dataType":"HoldingRegister","adr":"200","quantity":"100","server":"","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":true,"keepMsgProperties":true,"x":290,"y":420,"wires":[["cf904603.e7106"],[]]},{"id":"9f1d845b.532d8","type":"debug","z":"c7c6d4d7.58fc3","name":"Modbus Read","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":810,"y":480,"wires":[]},{"id":"d711d966.b30618","type":"debug","z":"c7c6d4d7.58fc3","name":"error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":160,"wires":[]},{"id":"50f4d5f8.e58de4","type":"function","z":"c7c6d4d7.58fc3","name":"error","func":"let 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\nif (msg.payload == \"\" || msg.hasOwnProperty('error')) {\n    \nnode.status({fill:\"red\",shape:\"ring\",text:`Last disconnected ${t}`});\nreturn [msg, null] // output error on first output\n}\n\nelse {\n    \nmsg.data1 = msg.payload\nreturn [null, msg];  // output valid msg on second output\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":180,"wires":[["d711d966.b30618"],["7e92c260.6f4cc4"]]},{"id":"7530ea64.384ca4","type":"function","z":"c7c6d4d7.58fc3","name":"error","func":"let 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\nif (msg.payload == \"\" || msg.hasOwnProperty('error')) {\n    \nnode.status({fill:\"red\",shape:\"ring\",text:`Last disconnected ${t}`});\nreturn [msg, null] // output error on first output\n}\n\nelse {\n    \nmsg.data2 = msg.payload\nreturn [null, msg];  // output valid msg on second output\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":300,"wires":[["9fe4b0a3.20daf"],["afcec612.c1d05"]]},{"id":"9fe4b0a3.20daf","type":"debug","z":"c7c6d4d7.58fc3","name":"error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":280,"wires":[]},{"id":"cf904603.e7106","type":"function","z":"c7c6d4d7.58fc3","name":"error","func":"let 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\nif (msg.payload == \"\" || msg.hasOwnProperty('error')) {\n    \nnode.status({fill:\"red\",shape:\"ring\",text:`Last disconnected ${t}`});\nreturn [msg, null] // output error on first output\n}\n\nelse {\n    \nmsg.data3 = msg.payload\nreturn [null, msg];  // output valid msg on second output\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":420,"wires":[["5abf456b.7e6a14"],["f0992446.7f13d8"]]},{"id":"5abf456b.7e6a14","type":"debug","z":"c7c6d4d7.58fc3","name":"error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":400,"wires":[]},{"id":"f0992446.7f13d8","type":"function","z":"c7c6d4d7.58fc3","name":"format msg","func":"msg.payload = [...msg.data1, ...msg.data2, ...msg.data3]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":480,"wires":[["9f1d845b.532d8"]]},{"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}]

Im not sure if the above helps but in my case i found it easier than using Status nodes because of the flexibility the Function node gives you to also modify the error msg to your needs.

Hello,
thanks for sharing.
My idea was to transfer the message to the dashboard, and beside the status to get to display the error message too.
I'm not sure how to extract the error message display by the debugging node.
.
Screenshot_2021-03-28 Node-RED Dashboard(2)
Screenshot_2021-03-28 Node-RED Dashboard(1)

I didn't study your exchange of code regarding the dashboard part (i got lost in the code) :sweat_smile:
but you could use Flow Context in each of the "error" functions to set the status for each of your devices

let 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());

if (msg.payload == "" || msg.hasOwnProperty('error')) {

node.status({fill:"red",shape:"ring",text:`Last disconnected ${t}`});
flow.set('Status.Device1', {"Status": "Failed", "lastDisconnected": t, "error": msg.error })  // set Status context failed
return [msg, null] // output error on first output
}

else {

msg.data1 = msg.payload
flow.set('Status.Device1', {"Status": "Active", "lastDisconnected": t, "error": ""})  // // set Status context ok
return [null, msg];  // output valid msg on second output
}

and later in the "generate alarm table" function read again the Flow context flow.get('Status') and incorporate the status in the table .. somehow.

Hi Steve,

Can you please help me with the correct syntax to extract the "S/N..." for the lookup list.
Thanks.

var lookup = {
  1: "Door Sensor", "S/N: 012456",
  2: "Window Sensor",  "S/N: 315352"
}

I have another question regarding deleting parts of the database.

I create database with this topic message:

CREATE TABLE 'alarmlog' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'Time' TEXT, 'Channel' TEXT, 'Alarm' TEXT, 'Status' TEXT, 'epoch' INTEGER, 'timestamp' INTEGER DEFAULT CURRENT_TIMESTAMP)

to display the last 24hours alarms I use this topic message:

let d = new Date();
let epoch = d.getTime();
let fromdate = 0;
let enddate = 0;

    fromdate = epoch - 1000*60*60*24*1;
    topic = "SELECT * FROM alarmlog WHERE epoch > "+fromdate;

msg.topic = topic+ " ORDER BY timestamp DESC";
return msg;

I want to delete all records older then 7 days, I try to use this topic but does work.

var d = new Date();
var epoch = d.getTime();
let fromdate = 0;

// today - 7 days (1000*60*60*24*7)
   fromdate = epoch - 1000*60*60*24*7;

topic = "DELETE FROM alarmlog WHERE epoch < "+fromdate;
msg.topic = topic+ " ORDER BY timestamp DESC LIMIT 10";
return msg;

Thanks for your help