Accessing SunSpec with Modbus TCP
This flow offers a generic approach for accessing SunSpec data via Modbus TCP communication based on the node-red-contrib-modbus module (https://flows.nodered.org/node/node-red-contrib-modbus).
It provides an example of reading data (Max PV Power and PV Power Limit) and an example of writing data (PV Power Limit).
It has been tested with the Enphase gateway and should work with any inverters that support Modbus TCP access and comply with SunSpec.
To understand what the data from the various Device Information Models corresponds to, refer to the SunSpec documentation (see https://sunspec.org/specifications/#sunspec-modbus2b84-f1df).
[{"id":"1b6376f996c2082d","type":"tab","label":"SunSpec","disabled":false,"info":"","env":[]},{"id":"5d7eaa70fc61da85","type":"modbus-flex-getter","z":"1b6376f996c2082d","name":"","showStatusActivities":true,"showErrors":true,"showWarnings":true,"logIOActivities":false,"server":"3df6c636aa6cabec","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":590,"y":220,"wires":[[],["fdbdeea371124ad0"]]},{"id":"ef94ac08bd7d6134","type":"inject","z":"1b6376f996c2082d","name":"Every 8h","props":[{"p":"topic","vt":"str"}],"repeat":"3600","crontab":"","once":true,"onceDelay":0.1,"topic":"Get map address","x":130,"y":100,"wires":[["b5f3328f5724ac1a"]]},{"id":"3bb964372e3930d0","type":"function","z":"1b6376f996c2082d","name":"Get Map Address","func":"let address_index = context.get('address_index');\nif (typeof address_index !== 'number' || msg.reset == 1) {\n address_index = 0;\n}\n\nif (address_index >= 3) {\n node.status({});\n throw \"Unable to find map address\";\n}\n\nlet map_addresses = [ 0, 40000, 50000 ];\nlet map_address = map_addresses[address_index];\nnode.status({fill:'grey',shape:'dot',text:'Address:' + map_address});\n\nmsg.payload = {\n unitid: 125,\n address: map_address,\n quantity: 2,\n fc: 3\n}\n\ncontext.set('address_index', address_index + 1);\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":220,"wires":[["5d7eaa70fc61da85"]]},{"id":"fdbdeea371124ad0","type":"function","z":"1b6376f996c2082d","name":"Set flow variable sunspec","func":"if (typeof msg.error === 'object') {\n flow.set('sunspec', null);\n node.status({fill:'red',shape:'dot',text:msg.error.message})\n} else if (msg.payload.buffer[0] == 0x53 && msg.payload.buffer[1] == 0x75 && msg.payload.buffer[2] == 0x6e && msg.payload.buffer[3] == 0x53) {\n // buffer[] = 'SunS' ?\n if (typeof msg.modbusRequest.address === 'number') {\n flow.set('sunspec', { map_address: msg.modbusRequest.address, dims: [] });\n node.status({fill:'grey',shape:'dot',text:'Map address: ' + msg.modbusRequest.address})\n return { payload: { address: msg.modbusRequest.address, length: 0 }, topic: 'Get DIM' }\n } else {\n throw \"Invalid map address\";\n }\n} else {\n throw \"Invalid map address\";\n}\n\nreturn null;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":220,"wires":[["3bc5b8dac24f1950"]]},{"id":"01b0a97d6d29a1e9","type":"catch","z":"1b6376f996c2082d","name":"","scope":["5d7eaa70fc61da85","fdbdeea371124ad0"],"uncaught":false,"x":150,"y":220,"wires":[["3bb964372e3930d0"]]},{"id":"6f66075d5739c9c2","type":"catch","z":"1b6376f996c2082d","name":"","scope":["3bb964372e3930d0"],"uncaught":false,"x":630,"y":180,"wires":[["fdbdeea371124ad0"]]},{"id":"3bc5b8dac24f1950","type":"function","z":"1b6376f996c2082d","name":"Get DIM","func":"let sunspec = flow.get('sunspec');\nif (typeof sunspec !== 'object' || !Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'})\n throw \"Invalid SunSpec\";\n}\n\nif (typeof msg.payload !== 'object' && typeof msg.payload.address !== 'number' && typeof msg.payload.length !== 'number') throw 'Invalid DIM';\n\n// DIM: Device Information Modele\nlet dim_address = msg.payload.address + 2 + msg.payload.length;\nnode.status({fill:'grey',shape:'dot',text:'DIM address:' + dim_address});\n\nmsg.payload = {\n unitid: 125,\n address: dim_address,\n quantity: 2,\n fc: 3\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":280,"wires":[["031f0a0859cd0aa1"]]},{"id":"031f0a0859cd0aa1","type":"modbus-flex-getter","z":"1b6376f996c2082d","name":"","showStatusActivities":true,"showErrors":true,"showWarnings":true,"logIOActivities":false,"server":"3df6c636aa6cabec","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":590,"y":280,"wires":[[],["528c9cae161128ab"]]},{"id":"528c9cae161128ab","type":"function","z":"1b6376f996c2082d","name":"Get DIM data || Complete","func":"let sunspec = flow.get('sunspec');\nif (!Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'});\n throw 'Invalid SunSpec';\n}\n\nif (typeof msg.modbusRequest.address !== 'number' || !Array.isArray(msg.payload.data)) {\n node.status({fill:'red',shape:'dot',text:'Invalid Modbus Response'});\n throw \"Invalid Modbus Response\";\n}\n\nif (msg.payload.data[0] != 0xffff && msg.payload.data[1] != 0) {\n dim = {\n address: msg.modbusRequest.address,\n id: msg.payload.data[0],\n length: msg.payload.data[1],\n data: null\n }\n\n sunspec.dims.push(dim); \n //global.set('sunspec', sunspec);\n node.status({fill:'grey',shape:'dot',text:'DIM: ' + dim.id + ' ('+dim.length+')'});\n \n // Limited to 124 bytes per request (Why ?);\n \n let msgs = [];\n let part = 1;\n let address = dim.address;\n for (let quantity = 2 + dim.length; quantity > 0; quantity -= 124) {\n msgs.push({\n topic: dim.id,\n payload: {\n unitid: 125,\n address: address,\n quantity: quantity>124 ? 124 : quantity,\n fc: 3\n },\n part: part\n });\n part++;\n address += 124;\n }\n \n msgs[msgs.length-1].complete = true;\n\n return [msgs];\n}\n\n// Complete\nglobal.set('sunspec', { ...sunspec });\n\nreturn null;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":280,"wires":[["85b55a0507f80d49"]]},{"id":"b8f8ddd1d1ba5f53","type":"catch","z":"1b6376f996c2082d","name":"","scope":["3bc5b8dac24f1950"],"uncaught":false,"x":150,"y":140,"wires":[["b5f3328f5724ac1a"]]},{"id":"b5f3328f5724ac1a","type":"delay","z":"1b6376f996c2082d","name":"","pauseType":"rate","timeout":"1","timeoutUnits":"minutes","rate":"1","nbRateUnits":"5","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":1,"x":320,"y":140,"wires":[["36d2472af7a2f366"]]},{"id":"27064e36b0b28205","type":"modbus-flex-getter","z":"1b6376f996c2082d","name":"","showStatusActivities":true,"showErrors":true,"showWarnings":true,"logIOActivities":false,"server":"3df6c636aa6cabec","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":true,"delayOnStart":false,"enableDeformedMessages":false,"startDelayTime":"","x":590,"y":440,"wires":[[],["8929637fbb3c165b"]]},{"id":"435e7b4056681fd3","type":"inject","z":"1b6376f996c2082d","name":"702 - Every 1m","props":[{"p":"topic","v":"702","vt":"num"}],"repeat":"60","crontab":"","once":true,"onceDelay":"65","topic":"","x":150,"y":440,"wires":[["898a41a07e84fce1"]]},{"id":"82559236fed8641c","type":"function","z":"1b6376f996c2082d","name":"Update SunSpec","func":"let id = msg.topic;\nif (typeof id !== 'number') {\n node.status({fill:'red',shape:'dot',text:'Invalid msg'})\n throw \"Invalid msg\";\n}\n\nlet address = Array.isArray(msg.payload) ? msg.payload[0].modbusRequest.address : msg.modbusRequest.address;\nif (typeof address !== 'number') {\n node.status({fill:'red',shape:'dot',text:'Invalid msg'})\n throw \"Invalid msg\";\n}\n\nlet sunspec = global.get('sunspec');\nif (typeof sunspec !== 'object' || !Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'})\n throw \"Invalid SunSpec\";\n}\n\nlet dim = sunspec.dims.find((element) => element.id == id && element.address == address);\n\nif (Array.isArray(msg.payload)) {\n dim.data = [];\n for (let item of msg.payload)\n dim.data = dim.data.concat(item.payload.data)\n} else {\n dim.data = msg.payload.data\n}\n\ndim.timestamp = Date.now();\n\nnode.status({fill:'grey',shape:'dot',text:'SunSpec ' + id + ' updated'})\n\nreturn null;","outputs":0,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":440,"wires":[]},{"id":"898a41a07e84fce1","type":"function","z":"1b6376f996c2082d","name":"Get DIM Data","func":"let id = msg.topic;\nif (typeof id !== 'number') {\n node.status({fill:'red',shape:'dot',text:'Invalid msg'})\n throw \"Invalid msg\";\n}\n\nlet sunspec = global.get('sunspec');\nif (typeof sunspec !== 'object' || !Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'})\n throw \"Invalid SunSpec\";\n}\n\nlet dim = sunspec.dims.find((element) => element.id == id);\ncontext.set('dim', dim);\nif (typeof dim !== 'object') {\n node.status({fill:'red',shape:'dot',text:'Missing SunSpec ' + id})\n throw \"Missing SunSpec \" + id;\n} else if (typeof dim.address !== 'number' || typeof dim.length !== 'number') {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec ' + id})\n throw \"Invalid SunSpec \" + id;\n}\n\nnode.status({fill:'grey',shape:'dot',text:'DIM address:' + dim.address});\n// Limited to 124 bytes per request (Why ?)\n\nlet msgs = [];\nlet part = 1;\nlet address = dim.address;\nfor (let quantity = 2 + dim.length; quantity > 0; quantity -= 124) {\n msgs.push({\n topic: dim.id,\n payload: {\n unitid: 125,\n address: address,\n quantity: quantity>124 ? 124 : quantity,\n fc: 3\n },\n part: part\n });\n part++;\n address += 124;\n}\n\nmsgs[msgs.length-1].complete = true;\n\nreturn [msgs];","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":440,"wires":[["27064e36b0b28205"]]},{"id":"f2cdd9210aee8e75","type":"inject","z":"1b6376f996c2082d","name":"704 - Every 1m","props":[{"p":"topic","v":"704","vt":"num"}],"repeat":"60","crontab":"","once":true,"onceDelay":"70","topic":"","x":150,"y":480,"wires":[["898a41a07e84fce1"]]},{"id":"36d2472af7a2f366","type":"function","z":"1b6376f996c2082d","name":"Reset","func":"msg.reset = 1;\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":140,"wires":[["3bb964372e3930d0"]]},{"id":"0201c6377a47ba63","type":"catch","z":"1b6376f996c2082d","name":"","scope":["82559236fed8641c","898a41a07e84fce1"],"uncaught":false,"x":90,"y":540,"wires":[["46483f132e25d02b"]]},{"id":"46483f132e25d02b","type":"switch","z":"1b6376f996c2082d","name":"","property":"error.message","propertyType":"msg","rules":[{"t":"neq","v":"Invalid msg","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":230,"y":540,"wires":[["07a37f9459fef48c"],[]]},{"id":"07a37f9459fef48c","type":"link out","z":"1b6376f996c2082d","name":"Reset SunSpec","mode":"link","links":["ca9fb72ca08261d6"],"x":335,"y":520,"wires":[]},{"id":"ca9fb72ca08261d6","type":"link in","z":"1b6376f996c2082d","name":"Reset","links":["07a37f9459fef48c"],"x":150,"y":180,"wires":[["b5f3328f5724ac1a"]],"l":true},{"id":"669e23173cf5929b","type":"comment","z":"1b6376f996c2082d","name":"Get SunSpec Map Address and Device Information Modele Adresses and Data","info":"","x":260,"y":60,"wires":[]},{"id":"47e4e21c8d91dec1","type":"comment","z":"1b6376f996c2082d","name":"Update some SunSpec Device Information Modele Data","info":"","x":220,"y":360,"wires":[]},{"id":"7e95bd24495f735d","type":"comment","z":"1b6376f996c2082d","name":"Read Data (Max PV Power, Actual PV Power Limit)","info":"","x":210,"y":620,"wires":[]},{"id":"48842a2b6be27b73","type":"inject","z":"1b6376f996c2082d","name":"Every 15s","props":[],"repeat":"60","crontab":"","once":true,"onceDelay":"90","topic":"","x":130,"y":680,"wires":[["ff9267f2c194d614","7a63bc3dcaf3cd84"]]},{"id":"ff9267f2c194d614","type":"function","z":"1b6376f996c2082d","name":"PV Power Limit","func":"let sunspec = global.get('sunspec');\nif (typeof sunspec !== 'object' || !Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'})\n throw \"Invalid SunSpec\";\n}\n\nlet dim702 = sunspec.dims.find((element) => element.id == 702);\nlet dim704 = sunspec.dims.find((element) => element.id == 704);\n\nif (!Array.isArray(dim702.data) || dim702.data.length != dim702.length+2) {\n node.status({fill:'red',shape:'dot',text:'Invalid data for SunSpec DIM 702'})\n} else if (dim702.timestamp < Date.now() - 180000) {\n node.status({fill:'red',shape:'dot',text:'Obsolet data for SunSpec DIM 702'})\n} else {\n if (!Array.isArray(dim704.data) || dim704.data.length != dim704.length+2) {\n node.status({fill:'red',shape:'dot',text:'Invalid data for SunSpec DIM 704'})\n } else if (dim704.timestamp < Date.now() - 180000) {\n node.status({fill:'red',shape:'dot',text:'Obsolet data for SunSpec DIM 704'})\n } else {\n let w_max = dim702.data[2];\n let lim_ena = dim704.data[14];\n let lim_pct = dim704.data[15];\n \n if (lim_ena != 0 && lim_ena != 1) {\n node.status({fill:'red',shape:'dot',text:'Invalid data for SunSpec DIM 704'})\n } else if (lim_pct < 0 || lim_pct > 100) {\n node.status({fill:'red',shape:'dot',text:'Invalid data for SunSpec DIM 704'})\n } else {\n text = lim_ena == 1 ? lim_pct + '% (~' + Math.round(w_max * lim_pct / 100) + 'W)' : 'Disabled';\n node.status({fill:'grey',shape:'dot',text:text})\n }\n }\n}\n\nreturn null;","outputs":0,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":700,"wires":[]},{"id":"7a63bc3dcaf3cd84","type":"function","z":"1b6376f996c2082d","name":"Max PV Power","func":"let sunspec = global.get('sunspec');\nif (typeof sunspec !== 'object' || !Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'})\n throw \"Invalid SunSpec\";\n}\n\nlet dim702 = sunspec.dims.find((element) => element.id == 702);\nif (!Array.isArray(dim702.data) || dim702.data.length != dim702.length+2) {\n node.status({fill:'red',shape:'dot',text:'Invalid data for SunSpec DIM 702'})\n} else if (dim702.timestamp < Date.now() - 28800000) {\n node.status({fill:'red',shape:'dot',text:'Obsolet data for SunSpec DIM 702'})\n} else {\n node.status({fill:'grey',shape:'dot',text:dim702.data[2] + 'W'})\n}\n\nreturn null;","outputs":0,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":660,"wires":[]},{"id":"98aca4b9eaa1594f","type":"comment","z":"1b6376f996c2082d","name":"Set PV Power Limit","info":"","x":110,"y":760,"wires":[]},{"id":"6a6c3bb51e4c2150","type":"inject","z":"1b6376f996c2082d","name":"0%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":110,"y":800,"wires":[["b3093ef075a990be"]]},{"id":"7ea7339ff1a902f5","type":"inject","z":"1b6376f996c2082d","name":"25%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25","payloadType":"num","x":110,"y":840,"wires":[["b3093ef075a990be"]]},{"id":"eaabaeae41d0f897","type":"inject","z":"1b6376f996c2082d","name":"100%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":110,"y":880,"wires":[["b3093ef075a990be"]]},{"id":"b3093ef075a990be","type":"function","z":"1b6376f996c2082d","name":"PV Power Limit","func":"let sunspec = global.get('sunspec');\nif (typeof sunspec !== 'object' || !Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'})\n throw \"Invalid SunSpec\";\n}\n\nlet dim704 = sunspec.dims.find((element) => element.id == 704);\nif (typeof dim704 !== 'object' || typeof dim704.address != 'number' || typeof dim704.length != 'number') {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec DIM 704'})\n throw \"Invalid SunSpec DIM 704\";\n}\n\nlet lim_pct = msg.payload;\nif (typeof lim_pct !== 'number' || lim_pct < 0 || lim_pct > 100) {\n node.status({fill:'red',shape:'dot',text:'Invalid value'})\n throw \"Invalid value\";\n}\n\nlet lim_ena = lim_pct == 100 ? 0 : 1;\n\nnode.status({fill:'grey',shape:'dot',text:'Set limit ' + lim_pct + '%'})\n\nmsg.payload = {\n unitid: 125,\n address: dim704.address + 14, // 40310\n quantity: 5,\n fc: 16,\n value: [ lim_ena, lim_pct, 0, 1, 120 ] // Data from Victron process. Why ?\n}\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":840,"wires":[["77b3319e6febb7d7"]]},{"id":"77b3319e6febb7d7","type":"modbus-flex-write","z":"1b6376f996c2082d","name":"","showStatusActivities":false,"showErrors":true,"showWarnings":true,"server":"3df6c636aa6cabec","emptyMsgOnFail":true,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":590,"y":840,"wires":[[],["99baa2d82c06b25e"]]},{"id":"99baa2d82c06b25e","type":"function","z":"1b6376f996c2082d","name":"Status","func":"// Fonction qui ajoute un 0 devant un nombre pour compléter la chaine de caractères\nconst pad = n => {\n return `${Math.floor(Math.abs(n))}`.padStart(2, '0');\n}\n// Function to return date as 'dd/mm hh:ii'\nconst toDateString = date => {\n return pad(date.getDate()) + '/' + pad(date.getMonth() + 1) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes())\n}\n\nlet fill = msg.error ? 'red' : 'green';\nlet text = msg.error ? msg.error.message : 'Complete';\n\nnode.status({fill:fill,shape:\"dot\",text: '[' + toDateString(new Date()) + '] ' + text});\n\nreturn null;","outputs":0,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":840,"wires":[]},{"id":"85b55a0507f80d49","type":"modbus-flex-getter","z":"1b6376f996c2082d","name":"","showStatusActivities":true,"showErrors":true,"showWarnings":true,"logIOActivities":false,"server":"3df6c636aa6cabec","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":true,"delayOnStart":false,"enableDeformedMessages":false,"startDelayTime":"","x":1070,"y":280,"wires":[[],["90fb2a9aa819a389"]]},{"id":"2c1f14c3e7ae3774","type":"function","z":"1b6376f996c2082d","name":"Update + Get Next","func":"let id = msg.topic;\nif (typeof id !== 'number') {\n node.status({fill:'red',shape:'dot',text:'Invalid msg'})\n throw \"Invalid msg\";\n}\n\nlet address = Array.isArray(msg.payload) ? msg.payload[0].modbusRequest.address : msg.modbusRequest.address;\nif (typeof address !== 'number') {\n node.status({fill:'red',shape:'dot',text:'Invalid msg'})\n throw \"Invalid msg\";\n}\n\nlet sunspec = flow.get('sunspec');\nif (typeof sunspec !== 'object' || !Array.isArray(sunspec.dims)) {\n node.status({fill:'red',shape:'dot',text:'Invalid SunSpec'})\n throw \"Invalid SunSpec\";\n}\n\nlet dim = sunspec.dims.find((element) => element.id == id && element.address == address);\n\nif (Array.isArray(msg.payload)) {\n dim.data = [];\n for (let item of msg.payload)\n dim.data = dim.data.concat(item.payload.data)\n} else {\n dim.data = msg.payload.data\n}\n\ndim.timestamp = Date.now();\nnode.status({fill:'grey',shape:'dot',text:'SunSpec ' + id + ' updated'})\n\n\nreturn { payload: dim, topic: 'Get DIM' };","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1430,"y":280,"wires":[["3bc5b8dac24f1950"]]},{"id":"90fb2a9aa819a389","type":"join","z":"1b6376f996c2082d","name":"","mode":"custom","build":"array","property":"","propertyType":"full","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1250,"y":280,"wires":[["2c1f14c3e7ae3774"]]},{"id":"8929637fbb3c165b","type":"join","z":"1b6376f996c2082d","name":"","mode":"custom","build":"array","property":"","propertyType":"full","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":770,"y":440,"wires":[["82559236fed8641c"]]},{"id":"29c7efaaaac6db74","type":"inject","z":"1b6376f996c2082d","name":"701 - Every 1m","props":[{"p":"topic","v":"701","vt":"num"}],"repeat":"60","crontab":"","once":true,"onceDelay":"60","topic":"","x":150,"y":400,"wires":[["898a41a07e84fce1"]]},{"id":"3df6c636aa6cabec","type":"modbus-client","name":"Enphase Gateway","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":true,"queueLogEnabled":true,"failureLogEnabled":true,"tcpHost":"192.168.219.12","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","serialAsciiResponseStartDelimiter":"0x3A","unit_id":126,"commandDelay":5,"clientTimeout":60000,"reconnectOnTimeout":true,"reconnectTimeout":120000,"parallelUnitIdsAllowed":true,"showErrors":true,"showWarnings":true,"showLogs":true}]