SDM120 single phase Modbus RTU energy meter
This flow shows how to set up Eastron SDM120 single phase energy meter under Node-Red. For this I am using node-red-contrib-modbus for the serial modbus communication. There are a few fucntion nodes titled "Diagnostic message*" which is used for my diagnostic process. That process is not included in the flow and I will only post about it the future. So ignore them for the time being, or just implement your own logic for those sections.
I have also posted a relatively long video on setting up the energy meter, changing the modbus settings and detailed explanation how the flow works: https://youtu.be/yBtqKSWDn1Q
[{"id":"b3ae0448.822d08","type":"comment","z":"2934a51a.93393a","name":"SDM120 Energy Meter","info":"","x":141.5,"y":1142,"wires":[]},{"id":"53b4b356.bf124c","type":"modbus-read","z":"2934a51a.93393a","name":"SDM 120 Voltage","showStatusActivities":true,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"0","quantity":"2","rate":"5","rateUnit":"s","server":"40f20c7f.13a934","x":113,"y":1235,"wires":[["e4d3fade.1132a8","1b35ee5d.7dd512"],[]]},{"id":"e4d3fade.1132a8","type":"function","z":"2934a51a.93393a","name":"Voltage","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = \"voltage\";\n\nnode.status({fill:\"blue\",shape:\"ring\",text:msg.topic + \":\" + msg.payload}); \n\nreturn msg;","outputs":1,"noerr":0,"x":342,"y":1228,"wires":[["94b7c2b7.b61ae","267d6c75.311c84"]]},{"id":"29fc9cac.869404","type":"modbus-queue-info","z":"2934a51a.93393a","name":"SDM120_queue","unitid":"30","lowLowLevel":"1","lowLevel":75,"highLevel":150,"highHighLevel":300,"server":"40f20c7f.13a934","errorOnHighLevel":false,"x":484.6428527832031,"y":1602.107177734375,"wires":[["759e42e9.23762c"]]},{"id":"27ead677.91de0a","type":"inject","z":"2934a51a.93393a","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":113.64286041259766,"y":1603.1072025299072,"wires":[["1d0b599c.544786"]]},{"id":"1d0b599c.544786","type":"change","z":"2934a51a.93393a","name":"Reset queue","rules":[{"t":"set","p":"resetQueue","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":282.64286041259766,"y":1603.1071891784668,"wires":[["29fc9cac.869404"]]},{"id":"e3643b3e.be3a88","type":"function","z":"2934a51a.93393a","name":"Current","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = \"current\";\n\nnode.status({fill:\"blue\",shape:\"ring\",text:msg.topic + \":\" + msg.payload}); \n\nreturn msg;","outputs":1,"noerr":0,"x":341,"y":1286,"wires":[["fa18b5f3.ba6be8","267d6c75.311c84"]]},{"id":"4dbf9277.487e4c","type":"function","z":"2934a51a.93393a","name":"Power","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = \"power\";\n\nnode.status({fill:\"blue\",shape:\"ring\",text:msg.topic + \":\" + msg.payload}); \n\nreturn msg;","outputs":1,"noerr":0,"x":332,"y":1339,"wires":[["c3697655.758e28","267d6c75.311c84"]]},{"id":"593c6698.c25388","type":"function","z":"2934a51a.93393a","name":"Frequency","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = \"frequency\";\n\nnode.status({fill:\"blue\",shape:\"ring\",text:msg.topic + \":\" + msg.payload}); \n\nreturn msg;","outputs":1,"noerr":0,"x":352,"y":1395,"wires":[["c38e8fed.7fb6a","267d6c75.311c84"]]},{"id":"7df027a2.13a1f8","type":"function","z":"2934a51a.93393a","name":"Energy","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(2));\nmsg.topic = \"energy\";\n\nnode.status({fill:\"blue\",shape:\"ring\",text:msg.topic + \":\" + msg.payload}); \n\nreturn msg;","outputs":1,"noerr":0,"x":342,"y":1455,"wires":[["d87b3ecd.2dab2","267d6c75.311c84"]]},{"id":"7d42a707.bcd618","type":"modbus-read","z":"2934a51a.93393a","name":"SDM 120 Current","showStatusActivities":true,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"6","quantity":"2","rate":"5","rateUnit":"s","server":"40f20c7f.13a934","x":122,"y":1293,"wires":[["e3643b3e.be3a88"],[]]},{"id":"576a7fbe.d0861","type":"modbus-read","z":"2934a51a.93393a","name":"SDM 120 Power","showStatusActivities":true,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"12","quantity":"2","rate":"5","rateUnit":"s","server":"40f20c7f.13a934","x":112,"y":1345,"wires":[["4dbf9277.487e4c"],[]]},{"id":"519b5a74.1a7314","type":"modbus-read","z":"2934a51a.93393a","name":"SDM 120 Frequency","showStatusActivities":true,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"70","quantity":"2","rate":"5","rateUnit":"s","server":"40f20c7f.13a934","x":132,"y":1401,"wires":[["593c6698.c25388"],[]]},{"id":"b84798db.04be98","type":"modbus-read","z":"2934a51a.93393a","name":"SDM 120 Energy","showStatusActivities":true,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"342","quantity":"2","rate":"5","rateUnit":"s","server":"40f20c7f.13a934","x":110,"y":1460,"wires":[["7df027a2.13a1f8","5fe7e9a5.d88c68"],[]]},{"id":"94b7c2b7.b61ae","type":"ui_text","z":"2934a51a.93393a","group":"2c2fdfeb.fe41e","order":0,"width":0,"height":0,"name":"","label":"Voltage","format":"{{msg.payload}} V","layout":"row-spread","x":528,"y":1230,"wires":[]},{"id":"fa18b5f3.ba6be8","type":"ui_text","z":"2934a51a.93393a","group":"2c2fdfeb.fe41e","order":0,"width":0,"height":0,"name":"","label":"Current","format":"{{msg.payload}} A","layout":"row-spread","x":529,"y":1285,"wires":[]},{"id":"c3697655.758e28","type":"ui_text","z":"2934a51a.93393a","group":"2c2fdfeb.fe41e","order":0,"width":0,"height":0,"name":"","label":"Power","format":"{{msg.payload}} W","layout":"row-spread","x":525,"y":1340,"wires":[]},{"id":"c38e8fed.7fb6a","type":"ui_text","z":"2934a51a.93393a","group":"2c2fdfeb.fe41e","order":0,"width":0,"height":0,"name":"","label":"Frequency","format":"{{msg.payload}} Hz","layout":"row-spread","x":545,"y":1396,"wires":[]},{"id":"d87b3ecd.2dab2","type":"ui_text","z":"2934a51a.93393a","group":"2c2fdfeb.fe41e","order":0,"width":0,"height":0,"name":"","label":"Total Energy","format":"{{msg.payload}} kWh","layout":"row-spread","x":547,"y":1456,"wires":[]},{"id":"267d6c75.311c84","type":"function","z":"2934a51a.93393a","name":"Build object","func":"watch_topic = \"energy\";\nvar output = {};\n\ncontext.set(msg.topic,msg.payload);\n\nif (context.get(\"voltage\")!==undefined) {\n output.voltage = context.get(\"voltage\");\n}\nif (context.get(\"current\")!==undefined) {\n output.current = context.get(\"current\");\n}\nif (context.get(\"power\")!==undefined) {\n output.power = context.get(\"power\");\n}\nif (context.get(\"frequency\")!==undefined) {\n output.frequency = context.get(\"frequency\");\n}\nif (context.get(\"energy\")!==undefined) {\n output.energy = context.get(\"energy\");\n}\nmsg.payload = output;\n\nif (msg.topic===watch_topic) {\n msg.topic = \"sdm120\";\n return msg;\n}","outputs":1,"noerr":0,"x":718,"y":1313,"wires":[["6d3190d1.e5037","30d370ab.48c66"]]},{"id":"6d3190d1.e5037","type":"debug","z":"2934a51a.93393a","name":"","active":false,"console":"false","complete":"true","x":882,"y":1313,"wires":[]},{"id":"78d7ebbc.cea674","type":"function","z":"2934a51a.93393a","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = \"High voltage warning: \" + msg.payload.voltage +\" V\";\nmsg.system = 1; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = \"Clean up step of the SAIA log backup has failed\";\nreturn msg;","outputs":1,"noerr":0,"x":1433,"y":1201,"wires":[["d9ab022c.d40aa"]]},{"id":"d9ab022c.d40aa","type":"link out","z":"2934a51a.93393a","name":"","links":["13e089a7.73cb46"],"x":1640,"y":1223,"wires":[]},{"id":"44460146.8cda4","type":"switch","z":"2934a51a.93393a","name":"Voltage check","property":"payload.voltage_check","propertyType":"msg","rules":[{"t":"eq","v":"high","vt":"str"},{"t":"eq","v":"low","vt":"str"}],"checkall":"true","outputs":2,"x":1141,"y":1225,"wires":[["78d7ebbc.cea674"],["a194e5c6.7a7eb8"]]},{"id":"a194e5c6.7a7eb8","type":"function","z":"2934a51a.93393a","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = \"Low voltage warning: \" + msg.payload.voltage +\" V\";\nmsg.system = 1; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = \"Clean up step of the SAIA log backup has failed\";\nreturn msg;","outputs":1,"noerr":0,"x":1433,"y":1246,"wires":[["d9ab022c.d40aa"]]},{"id":"30d370ab.48c66","type":"function","z":"2934a51a.93393a","name":"Voltage check","func":"var high = 250.0;\nvar low = 220.0;\n\nif (msg.payload.voltage > high) {\n msg.payload.voltage_check = \"high\";\n}\nif (msg.payload.voltage < low) {\n msg.payload.voltage_check = \"low\";\n}\nreturn msg;","outputs":1,"noerr":0,"x":928,"y":1226,"wires":[["44460146.8cda4"]]},{"id":"d01ace0b.7d3d4","type":"function","z":"2934a51a.93393a","name":"reset on HighHigh","func":"if(\"high high level reached\" === msg.state) {\n msg.resetQueue = true;\n return msg;\n}\n","outputs":1,"noerr":0,"x":357.50000762939453,"y":1733.2500247955322,"wires":[["29fc9cac.869404","dc784194.a11f6"]]},{"id":"ec676e18.9bbae","type":"catch","z":"2934a51a.93393a","name":"Catch queue errors","scope":["29fc9cac.869404"],"x":119.50000762939453,"y":1733.2500247955322,"wires":[["d01ace0b.7d3d4"]]},{"id":"284bdf25.d13a9","type":"comment","z":"2934a51a.93393a","name":"Error handling","info":"","x":85.50000762939453,"y":1688.2500247955322,"wires":[]},{"id":"dc784194.a11f6","type":"function","z":"2934a51a.93393a","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = \"SDM120 modbus queue reached high level, resetting. (\" + msg.state + \")\";\nmsg.system = 4; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = \"Clean up step of the SAIA log backup has failed\";\nreturn msg;","outputs":1,"noerr":0,"x":657.5000076293945,"y":1732.2500247955322,"wires":[["5c63f003.554"]]},{"id":"5c63f003.554","type":"link out","z":"2934a51a.93393a","name":"","links":["13e089a7.73cb46"],"x":843.5000076293945,"y":1732.2500247955322,"wires":[]},{"id":"e1c40b2e.6b7978","type":"catch","z":"2934a51a.93393a","name":"Modbus read errors","scope":["7d42a707.bcd618","b84798db.04be98","519b5a74.1a7314","576a7fbe.d0861","53b4b356.bf124c","b3ae0448.822d08"],"x":119.50000762939453,"y":1776.2500247955322,"wires":[["c8d0574c.839038"]]},{"id":"c8d0574c.839038","type":"function","z":"2934a51a.93393a","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = \"SDM120 modbus error: \" + msg.error.message;\nmsg.system = 4; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = \"Clean up step of the SAIA log backup has failed\";\nreturn msg;","outputs":1,"noerr":0,"x":408.50000762939453,"y":1776.2500247955322,"wires":[["89cc7882.52fb68"]]},{"id":"89cc7882.52fb68","type":"link out","z":"2934a51a.93393a","name":"","links":["13e089a7.73cb46"],"x":594.5000076293945,"y":1776.2500247955322,"wires":[]},{"id":"759e42e9.23762c","type":"debug","z":"2934a51a.93393a","name":"","active":true,"console":"false","complete":"true","x":665.5000076293945,"y":1601.2500247955322,"wires":[]},{"id":"5fe7e9a5.d88c68","type":"function","z":"2934a51a.93393a","name":"Health check","func":"var devicename = \"sdm120\"; // Device name used for context variable\nvar system_id = 4; // System id number for diagnostic update\nvar online_threshold = 10; // Seconds between updates under which the device is considered online\nvar offline_threshold = 30; // Seconds between updates above which the device is considered offline\n\nvar temp = context.get(devicename+\"_update\");\nvar current = new Date();\nmsg.payload = \"No data\";\nmsg.warning = false;\nif (msg.topic!==\"timecheck\") {\n // Do not update the context if it is triggered by the check inject node\n context.set(devicename+\"_update\",current);\n}\nif (temp===undefined) {\n // this will be the case when node-red is booting up or redeployed\n context.set(devicename+\"_update\",current);\n}\n\nif (temp!==undefined) {\n current = current - temp;\n current = Math.floor(current/1000);\n var minute = Math.floor(current/60);\n var hour = Math.floor(minute/60);\n var day = Math.floor(hour/24);\n if (current>24*60*60) {\n msg.payload = \"Last update \" + day + \" days, \" + hour%24 + \" hours, \" + minute%60 + \" minutes, \" + current%60 + \" seconds ago\";\n } else if (current>60*60) {\n msg.payload = \"Last update \" + hour%24 + \" hours, \" + minute%60 + \" minutes, \" + current%60 + \" seconds ago\";\n } else if (current>60) {\n msg.payload = \"Last update \" + minute%60 + \" minutes, \" + current%60 + \" seconds ago\";\n } else {\n msg.payload = \"Last update \" + current%60 + \" seconds ago\";\n }\n\n if (context.get(devicename+\"_state\")!==1) {\n if (current<online_threshold) {\n msg.payload = \"SDM120 is now online\";\n msg.system = system_id; // System id, use 1 for Dummy\n msg.state = 1; // specify if the message is to change system status\n msg.severity = 0; // 0: information, 1: warning, 2: error\n //msg.email = true; // if separate email should be sent\n //msg.emailtext = \"\"; this a long text which goes into the email \n msg.warning = true;\n context.set(devicename+\"_state\",1);\n }\n } else {\n if (current>offline_threshold) {\n msg.payload = \"SDM120 is not transmitting\";\n msg.system = system_id; // System id, use 1 for Dummy\n msg.state = 99; // specify if the message is to change system status\n msg.severity = 2; // 0: information, 1: warning, 2: error\n //msg.email = true; // if separate email should be sent\n //msg.emailtext = \"\"; this a long text which goes into the email \n msg.warning = true;\n context.set(devicename+\"_state\",99);\n }\n }\n \n \n}\n\nnode.status({fill:\"blue\",shape:\"ring\",text:msg.payload});\n\nreturn msg;","outputs":1,"noerr":0,"x":366,"y":1520,"wires":[["e8795434.be1748"]]},{"id":"4646391.9ea9cc8","type":"inject","z":"2934a51a.93393a","name":"Check","topic":"timecheck","payload":"","payloadType":"date","repeat":"1","crontab":"","once":false,"x":150,"y":1532,"wires":[["5fe7e9a5.d88c68"]]},{"id":"e8795434.be1748","type":"switch","z":"2934a51a.93393a","name":"Update diag?","property":"warning","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","outputs":1,"x":561,"y":1520,"wires":[["ee08c280.18881"]]},{"id":"ee08c280.18881","type":"link out","z":"2934a51a.93393a","name":"","links":["13e089a7.73cb46"],"x":680.3015727996826,"y":1519.571403503418,"wires":[]},{"id":"1b35ee5d.7dd512","type":"debug","z":"2934a51a.93393a","name":"","active":true,"console":"false","complete":"false","x":338,"y":1176,"wires":[]},{"id":"40f20c7f.13a934","type":"modbus-client","z":"","name":"Serial_9600_8_N_1","clienttype":"serial","bufferCommands":true,"stateLogEnabled":false,"tcpHost":"127.0.0.1","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB0","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":"","commandDelay":"30","clientTimeout":"2000","reconnectTimeout":"5000"},{"id":"2c2fdfeb.fe41e","type":"ui_group","z":"","name":"SDM120 Energy Meter","tab":"72b1a4dc.4f488c","order":2,"disp":true,"width":"6"},{"id":"72b1a4dc.4f488c","type":"ui_tab","z":"","name":"Modbus","icon":"memory","order":15}]