Testing a PLC program - IO Simulation

It is in support of my video https://www.youtube.com/watch?v=6n6fTxdmspQ

Some basic info:

  1. PLC is Allen Bradley ControlLogix L61
  2. Demo Program is for hydraulic winch (heavily simplified)
  3. Simulation environment: Node-RED with node-red-contrib-cip-ethernet-ip module

This is all part of framework at https://www.controlx.io

[{"id":"fa3843f7.68d0b","type":"tab","label":"Sim Operator","disabled":false,"info":""},{"id":"fea35f7b.4a29a","type":"tab","label":"Hydraulics","disabled":false,"info":""},{"id":"583f4d36.50aae4","type":"eth-ip endpoint","z":"","address":"192.168.98.1","slot":"0","cycletime":"100","name":"","vartable":{"":{"Local:3:O.Data.0":{"type":"BOOL"},"Local:3:O.Data.1":{"type":"BOOL"},"Local:3:O.Data.2":{"type":"BOOL"},"Local:3:O.Data.3":{"type":"BOOL"},"Local:2:I.Data.0":{"type":"BOOL"},"Local:2:I.Data.1":{"type":"BOOL"},"Local:2:I.Data.2":{"type":"BOOL"},"Local:2:I.Data.3":{"type":"BOOL"},"Local:2:I.Data.4":{"type":"BOOL"},"Local:2:I.Data.5":{"type":"BOOL"},"Local:2:I.Data.6":{"type":"BOOL"},"Local:2:I.Data.7":{"type":"BOOL"},"Local:2:I.Data.8":{"type":"BOOL"},"Local:2:I.Data.9":{"type":"BOOL"},"Local:2:I.Data.10":{"type":"BOOL"},"Local:2:I.Data.11":{"type":"BOOL"},"Local:2:I.Data.12":{"type":"BOOL"},"Local:2:I.Data.13":{"type":"BOOL"},"Local:2:I.Data.14":{"type":"BOOL"},"Local:2:I.Data.15":{"type":"BOOL"},"Local:2:I.Data.16":{"type":"BOOL"},"Local:4:I.Ch0Data":{"type":"REAL"}}}},{"id":"1f5894f7.5861bb","type":"ui_group","z":"","name":"Simulated Operator's Panel","tab":"c46bed3b.020d7","order":2,"disp":true,"width":"6","collapse":false},{"id":"c46bed3b.020d7","type":"ui_tab","z":"","name":"Winch Simulation","icon":"dashboard","disabled":false,"hidden":false},{"id":"b6fd3c8b.d23a3","type":"ui_base","theme":{"name":"theme-light","lightTheme":{"default":"#0094CE","baseColor":"#0094CE","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":true,"reset":false},"darkTheme":{"default":"#097479","baseColor":"#097479","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":false},"customTheme":{"name":"Untitled Theme 1","default":"#4B7930","baseColor":"#4B7930","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"},"themeState":{"base-color":{"default":"#0094CE","value":"#0094CE","edited":false},"page-titlebar-backgroundColor":{"value":"#0094CE","edited":false},"page-backgroundColor":{"value":"#fafafa","edited":false},"page-sidebar-backgroundColor":{"value":"#ffffff","edited":false},"group-textColor":{"value":"#1bbfff","edited":false},"group-borderColor":{"value":"#ffffff","edited":false},"group-backgroundColor":{"value":"#ffffff","edited":false},"widget-textColor":{"value":"#111111","edited":false},"widget-backgroundColor":{"value":"#0094ce","edited":false},"widget-borderColor":{"value":"#ffffff","edited":false},"base-font":{"value":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"}},"angularTheme":{"primary":"indigo","accents":"blue","warn":"red","background":"grey"}},"site":{"name":"Simulation Demo","hideToolbar":"false","allowSwipe":"false","lockMenu":"false","allowTempTheme":"true","dateFormat":"DD/MM/YYYY","sizes":{"sx":48,"sy":48,"gx":6,"gy":6,"cx":6,"cy":6,"px":0,"py":0}}},{"id":"d6caabb5.09a3d8","type":"ui_group","z":"","name":"Simulation Outputs","tab":"c46bed3b.020d7","order":3,"disp":true,"width":"6","collapse":false},{"id":"8b42adca.7805b","type":"ui_group","z":"","name":"Default","tab":"c46bed3b.020d7","order":1,"disp":false,"width":"6","collapse":false},{"id":"9c85ac7d.87f5b","type":"eth-ip in","z":"fea35f7b.4a29a","endpoint":"583f4d36.50aae4","mode":"single","variable":"Local:3:O.Data.1","program":"","name":"","x":140,"y":220,"wires":[["592948f2.64f108"]]},{"id":"98c654e4.cb8bd8","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.0","program":"","name":"Start PB","x":920,"y":160,"wires":[]},{"id":"70af2a8b.dc7414","type":"inject","z":"fa3843f7.68d0b","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":110,"y":160,"wires":[["f2fa1f2e.42307","983a7d52.37737","3808bcd0.693014"]]},{"id":"f47ce61d.32f088","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.1","program":"","name":"Stop PB","x":920,"y":220,"wires":[]},{"id":"4635604a.9ab74","type":"function","z":"fa3843f7.68d0b","name":"3 pole switch","func":"\"use strict\";\n\nif (msg.payload !== \"all\" && !Number.isInteger(msg.payload)) return;\n\nconst length = 4;\nconst msgs = (msg.payload === \"all\")?\n    Array(length).fill(buildMsg(true)):\n    Array(length).fill(buildMsg(false));\n\nif (msg.payload > 0) {\n    msgs[0] = null;\n    msgs[parseInt(msg.payload)] = buildMsg(true);\n}\n\nreturn msgs\n\nfunction buildMsg(value){\n    return {payload: value, topic: msg.topic}\n}","outputs":4,"noerr":0,"x":670,"y":420,"wires":[[],["f0ce2063.e9039"],["8d2c03b.a6113"],["3454c985.bd8ea6"]],"outputLabels":["[reserved]","1st option selected","2nd option  selected","3rd option  selected"]},{"id":"736e5151.471c1","type":"ui_dropdown","z":"fa3843f7.68d0b","name":"","label":"SS1 Operation Type","tooltip":"","place":"Select option","group":"1f5894f7.5861bb","order":4,"width":0,"height":0,"passthru":true,"options":[{"label":"Error (none)","value":0,"type":"num"},{"label":"Auto","value":1,"type":"num"},{"label":"Manual","value":2,"type":"num"},{"label":"Maint","value":3,"type":"num"},{"label":"Error (all)","value":"all","type":"str"}],"payload":"","topic":"","x":460,"y":420,"wires":[["4635604a.9ab74"]]},{"id":"f0ce2063.e9039","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.2","program":"","name":"SS1 Auto Mode","x":940,"y":360,"wires":[]},{"id":"8d2c03b.a6113","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.3","program":"","name":"SS1 Manual Mode","x":950,"y":420,"wires":[]},{"id":"3454c985.bd8ea6","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.4","program":"","name":"SS1 Maintenance Mode","x":970,"y":480,"wires":[]},{"id":"983a7d52.37737","type":"ui_template","z":"fa3843f7.68d0b","group":"1f5894f7.5861bb","name":"STOP","order":2,"width":0,"height":0,"format":"<script>\n    this.scope.sendPayload = function(_scope, value) {\n        // const topic = \"_topic\";\n        \n        if (value === \"out\" && _scope.lastValue)\n            return _scope.send(buildMsg(false));\n        \n        if (typeof value !== \"boolean\") return;\n        \n        _scope.send(buildMsg(value));\n        \n        function buildMsg(_payload, _topic){\n            _scope.lastValue = _payload;\n            return {payload: _payload, topic: _topic}\n        }\n    }\n</script>\n<md-button\n    ng-touchstart=\"sendPayload(this, true)\"\n    ng-touchend=\"sendPayload(this, false)\"\n    ng-mousedown=\"sendPayload(this, true)\"\n    ng-mouseup=\"sendPayload(this, false)\"\n    ng-mouseleave=\"sendPayload(this, 'out')\">\n        PB2 STOP\n</md-button>","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":450,"y":220,"wires":[["f47ce61d.32f088"]]},{"id":"f2fa1f2e.42307","type":"ui_template","z":"fa3843f7.68d0b","group":"1f5894f7.5861bb","name":"START","order":1,"width":0,"height":0,"format":"<script>\n    this.scope.sendPayload = function(_scope, value) {\n        // const topic = \"_topic\";\n        \n        if (value === \"out\" && _scope.lastValue)\n            return _scope.send(buildMsg(false));\n        \n        if (typeof value !== \"boolean\") return;\n        \n        _scope.send(buildMsg(value));\n        \n        function buildMsg(_payload, _topic){\n            _scope.lastValue = _payload;\n            return {payload: _payload, topic: _topic}\n        }\n    }\n</script>\n<md-button\n    ng-touchstart=\"sendPayload(this, true)\"\n    ng-touchend=\"sendPayload(this, false)\"\n    ng-mousedown=\"sendPayload(this, true)\"\n    ng-mouseup=\"sendPayload(this, false)\"\n    ng-mouseleave=\"sendPayload(this, 'out')\">\n        PB1 START\n</md-button>","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":460,"y":160,"wires":[["98c654e4.cb8bd8"]]},{"id":"3808bcd0.693014","type":"ui_template","z":"fa3843f7.68d0b","group":"1f5894f7.5861bb","name":"RESET","order":2,"width":0,"height":0,"format":"<script>\n    this.scope.sendPayload = function(_scope, value) {\n        // const topic = \"_topic\";\n        \n        if (value === \"out\" && _scope.lastValue)\n            return _scope.send(buildMsg(false));\n        \n        if (typeof value !== \"boolean\") return;\n        \n        _scope.send(buildMsg(value));\n        \n        function buildMsg(_payload, _topic){\n            _scope.lastValue = _payload;\n            return {payload: _payload, topic: _topic}\n        }\n    }\n</script>\n<md-button\n    ng-touchstart=\"sendPayload(this, true)\"\n    ng-touchend=\"sendPayload(this, false)\"\n    ng-mousedown=\"sendPayload(this, true)\"\n    ng-mouseup=\"sendPayload(this, false)\"\n    ng-mouseleave=\"sendPayload(this, 'out')\">\n        PB3 RESET\n</md-button>","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":460,"y":280,"wires":[["1faa0141.05bbef"]]},{"id":"1faa0141.05bbef","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.5","program":"","name":"Reset PB","x":920,"y":280,"wires":[]},{"id":"39433b28.779c94","type":"eth-ip out","z":"fea35f7b.4a29a","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.10","program":"","name":"","x":950,"y":260,"wires":[]},{"id":"5e4bb611.72e6b8","type":"eth-ip out","z":"fea35f7b.4a29a","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.11","program":"","name":"","x":950,"y":320,"wires":[]},{"id":"df1d7c8d.cafc1","type":"eth-ip in","z":"fea35f7b.4a29a","endpoint":"583f4d36.50aae4","mode":"single","variable":"Local:3:O.Data.0","program":"","name":"","x":140,"y":120,"wires":[["9b0fd6b5.d10f68","87f800e6.3ef1a"]]},{"id":"634967fe.42f608","type":"eth-ip out","z":"fea35f7b.4a29a","endpoint":"583f4d36.50aae4","variable":"Local:4:I.Ch0Data","program":"","name":"","x":950,"y":120,"wires":[]},{"id":"83820e82.96962","type":"ui_switch","z":"fa3843f7.68d0b","name":"","label":"SS2 Extend Base","tooltip":"","group":"1f5894f7.5861bb","order":6,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","x":480,"y":700,"wires":[["17281267.c1b39e"]]},{"id":"17281267.c1b39e","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.6","program":"","name":"","x":940,"y":700,"wires":[]},{"id":"4ab215af.21d83c","type":"inject","z":"fa3843f7.68d0b","name":"","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":130,"y":700,"wires":[["83820e82.96962"]]},{"id":"b0e3a181.cea53","type":"function","z":"fea35f7b.4a29a","name":"Hydraulic Cylinder","func":"\"use strict\";\n// Outputs: 1 - position value; 2 - Max position; 3 - Min Position; 4 - at setpoint\n\n// how long does it take to reach from extended to retracted position\nconst time_ms = 5000;\n// length to travel in [mm]\nconst length = 2000;\n// Mid point setpoint, simulates a limit switch position in [mm] from retracted limit \nconst spLength = 500;\n\n\n// ---- DO NOT CHANGE VALUES BELOW ---- \nconst minPosition = 0;\nconst req = {\n    stop: 0,\n    forwardStart: 1,\n    reverseStart: -1\n}\nconst update_ms = 150;\nconst step = length/(time_ms/update_ms);\n\nif (typeof msg._extend !== \"boolean\" &&  typeof msg._retract !== \"boolean\") return;\nconst dirRequest = getRequest(msg._extend, msg._retract, context.get(\"dirRequest\"));\n\nif (dirRequest === null) return;\n\ncontext.set(\"dirRequest\", dirRequest);\n\n// if (typeof msg.payload !== \"number\") return;\n// const dirRequest = (msg.payload > 0)? 1: (msg.payload < 0)? -1 : 0;\n// if (context.get(\"dirRequest\") === dirRequest) return;\n\n// node.log(`dirRequest=${dirRequest}, interval=${context.get(\"interval\")}`)\nnode.status({text:`Direction = ${dirRequest}`});\n\nif (dirRequest === req.stop) {\n    clearInterval(context.get(\"interval\"));\n    return;\n}\n\nlet position = context.get(\"position\") || minPosition;\n\nclearInterval(context.get(\"interval\"));\ncontext.set(\"interval\", setInterval(calculateMove, update_ms));\n\nlet out4Sent = false;\nfunction calculateMove() {\n    if (dirRequest === req.forwardStart && position >= length) {\n        position = length;\n        reachedLimit(true)\n    } else if (dirRequest === req.reverseStart && position <= minPosition) {\n        position = minPosition;\n        reachedLimit(false)\n    } else {\n        if (position === length || position === minPosition) \n            node.send([null, buildMsg(false), buildMsg(false), buildMsg(false)]);\n        position = position + dirRequest * step;\n        context.set(\"position\", position);\n        node.send([buildMsg(position)]);\n        // calculate and send middle position\n        if (position >= (spLength - step/2) && position < (spLength + step/2)) {\n            out4Sent = true;\n            node.send([null, null, null, buildMsg(true)]);\n        } else if (out4Sent) {\n            out4Sent = false;\n            node.send([null, null, null, buildMsg(false)]);\n        }\n    }\n}\n\nfunction reachedLimit(value) {\n    context.set(\"position\", position);\n    clearInterval(context.get(\"interval\"));\n    context.set(\"interval\", null);\n    node.send([buildMsg(position), buildMsg(value), buildMsg(!value), buildMsg(false)]);\n}\n\nfunction buildMsg(value) {\n    return {payload: value, topic: msg.topic}\n}\n\n\nfunction getRequest(_extend, _retract, lastRequest) {\n\n    if (_extend === true) return req.forwardStart;\n    if (_retract === true) return req.reverseStart;\n    if (_extend === false && lastRequest === req.forwardStart) return req.stop;\n    if (_retract === false && lastRequest === req.reverseStart) return req.stop;\n    \n    return null\n    // const forwardStart = (msg.payload && msg.topic !== \"_extend\");\n    // const forwardStop = (!msg.payload && msg.topic !== \"_extend\");\n    \n    // const reverseStart = (msg.payload && msg.topic !== \"_retract\");\n    // const reverseStop = (!msg.payload && msg.topic !== \"_retract\");\n}","outputs":4,"noerr":0,"x":630,"y":220,"wires":[["deefadf1.a1e69"],["39433b28.779c94"],["5e4bb611.72e6b8"],[]],"outputLabels":["Position Value","Extended","Retracted","In Mid SP"]},{"id":"592948f2.64f108","type":"function","z":"fea35f7b.4a29a","name":"Prepare signal","func":"// must ba a boolean\nif (typeof msg.payload !== \"boolean\" && msg.topic !== \"pressure\") return;\n\nconst prSP = 40;\n\nif (msg.topic === \"pressure\") {\n    context.set(\"pressure\", msg.payload)\n    \n    if (msg.payload <= prSP) return {_extend: false, _retract: false};\n\n} else {\n    if (msg.payload) {\n        context.set(\"_extend\", true);\n        context.set(\"_retract\", false);\n    } else {\n        context.set(\"_extend\", false);\n        context.set(\"_retract\", true);\n    }\n}\n\nif (context.get(\"pressure\") > prSP)\n    return {_extend: context.get(\"_extend\"), _retract: context.get(\"_retract\")}","outputs":1,"noerr":0,"x":380,"y":220,"wires":[["b0e3a181.cea53"]],"inputLabels":["Boolean value"]},{"id":"deefadf1.a1e69","type":"ui_text","z":"fea35f7b.4a29a","group":"d6caabb5.09a3d8","order":3,"width":0,"height":0,"name":"","label":"Base Position","format":"{{(msg.payload === 0)? 'PARKED': (msg.payload === 2000)? 'EXTENDED' : (msg.payload).toFixed(2)}}","layout":"row-spread","x":940,"y":200,"wires":[]},{"id":"9b0fd6b5.d10f68","type":"ui_text","z":"fea35f7b.4a29a","group":"d6caabb5.09a3d8","order":1,"width":0,"height":0,"name":"","label":"Hyd Motor","format":"{{(msg.payload === true)? \"ON\" : \"OFF\"}}","layout":"row-spread","x":930,"y":60,"wires":[]},{"id":"23e26343.423a8c","type":"ui_text","z":"fea35f7b.4a29a","group":"d6caabb5.09a3d8","order":2,"width":0,"height":0,"name":"","label":"Pressure","format":"{{msg.payload.toFixed(2)}}, bar","layout":"row-spread","x":1140,"y":60,"wires":[]},{"id":"87f800e6.3ef1a","type":"function","z":"fea35f7b.4a29a","name":"Anaolog Value Rise","func":"\"use strict\";\n// Outputs: 1 - current value; 2 - Max value; 3 - Min value\n\n// how long does it take to reach from extended to retracted position\nconst time_ms = 2000;\n// Max Analog Value\nconst analogValue = 50;\n\n\n// ---- DO NOT CHANGE VALUES BELOW ---- \nconst spLength = 500;\nconst minPosition = 0;\nconst req = {\n    stop: 0,\n    forwardStart: 1,\n    reverseStart: -1\n}\nconst update_ms = 100;\nconst step = analogValue/(time_ms/update_ms);\n\nif (typeof msg.payload !== \"boolean\") return;\nconst dirRequest = getRequest(msg.payload, !msg.payload, context.get(\"dirRequest\"));\n\nif (dirRequest === null) return;\n\ncontext.set(\"dirRequest\", dirRequest);\n\n// if (typeof msg.payload !== \"number\") return;\n// const dirRequest = (msg.payload > 0)? 1: (msg.payload < 0)? -1 : 0;\n// if (context.get(\"dirRequest\") === dirRequest) return;\n\n// node.log(`dirRequest=${dirRequest}, interval=${context.get(\"interval\")}`)\nnode.status({text:`Direction = ${dirRequest}`});\n\nif (dirRequest === req.stop) {\n    clearInterval(context.get(\"interval\"));\n    return;\n}\n\nlet position = context.get(\"position\") || minPosition;\n\nclearInterval(context.get(\"interval\"));\ncontext.set(\"interval\", setInterval(calculateMove, update_ms));\n\nlet out4Sent = false;\nfunction calculateMove() {\n    if (dirRequest === req.forwardStart && position >= analogValue) {\n        position = analogValue;\n        reachedLimit(true)\n    } else if (dirRequest === req.reverseStart && position <= minPosition) {\n        position = minPosition;\n        reachedLimit(false)\n    } else {\n        if (position === analogValue || position === minPosition) \n            node.send([null, buildMsg(false), buildMsg(false), buildMsg(false)]);\n        position = position + dirRequest * step;\n        context.set(\"position\", position);\n        node.send([buildMsg(position)]);\n        // calculate and send middle position\n        if (position >= (spLength - step/2) && position < (spLength + step/2)) {\n            out4Sent = true;\n            node.send([null, null, null, buildMsg(true)]);\n        } else if (out4Sent) {\n            out4Sent = false;\n            node.send([null, null, null, buildMsg(false)]);\n        }\n    }\n}\n\nfunction reachedLimit(value) {\n    context.set(\"position\", position);\n    clearInterval(context.get(\"interval\"));\n    context.set(\"interval\", null);\n    node.send([buildMsg(position), buildMsg(value), buildMsg(!value), buildMsg(false)]);\n}\n\nfunction buildMsg(value) {\n    return {payload: value, topic: msg.topic}\n}\n\n\nfunction getRequest(_extend, _retract, lastRequest) {\n\n    if (_extend === true) return req.forwardStart;\n    if (_retract === true) return req.reverseStart;\n    if (_extend === false && lastRequest === req.forwardStart) return req.stop;\n    if (_retract === false && lastRequest === req.reverseStart) return req.stop;\n    \n    return null\n}","outputs":3,"noerr":0,"x":630,"y":120,"wires":[["23e26343.423a8c","634967fe.42f608","274475fa.39477a","274475fa.39477a"],[],[]],"outputLabels":["Position Value","Extended","Retracted","In Mid SP"]},{"id":"274475fa.39477a","type":"function","z":"fea35f7b.4a29a","name":"topic","func":"msg.topic = \"pressure\";\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":160,"wires":[["592948f2.64f108","592948f2.64f108"]]},{"id":"9d1d94f5.e5cb58","type":"ui_slider","z":"fa3843f7.68d0b","name":"","label":"Joystik","tooltip":"Wind In <===> Wind Out","group":"1f5894f7.5861bb","order":5,"width":0,"height":0,"passthru":true,"outs":"end","topic":"","min":"-1","max":"1","step":1,"x":430,"y":540,"wires":[["19cb8c52.06d494","2ec1fd55.008d32"]]},{"id":"19cb8c52.06d494","type":"function","z":"fa3843f7.68d0b","name":"Greater 0","func":"msg.payload = (msg.payload > 0)\nreturn msg;","outputs":1,"noerr":0,"x":660,"y":600,"wires":[["f08aab81.574178"]]},{"id":"2ec1fd55.008d32","type":"function","z":"fa3843f7.68d0b","name":"Less 0","func":"msg.payload = (msg.payload < 0)\nreturn msg;","outputs":1,"noerr":0,"x":650,"y":540,"wires":[["e6623556.5d0708"]]},{"id":"e6623556.5d0708","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.7","program":"","name":"","x":940,"y":540,"wires":[]},{"id":"f08aab81.574178","type":"eth-ip out","z":"fa3843f7.68d0b","endpoint":"583f4d36.50aae4","variable":"Local:2:I.Data.8","program":"","name":"","x":940,"y":600,"wires":[]},{"id":"d1acec7c.47745","type":"eth-ip in","z":"fea35f7b.4a29a","endpoint":"583f4d36.50aae4","mode":"single","variable":"Local:3:O.Data.2","program":"","name":"","x":140,"y":400,"wires":[["6d7bc8c7.0a2988"]]},{"id":"726503e6.6029bc","type":"eth-ip in","z":"fea35f7b.4a29a","endpoint":"583f4d36.50aae4","mode":"single","variable":"Local:3:O.Data.3","program":"","name":"","x":140,"y":460,"wires":[["bd07a1e1.0d84f"]]},{"id":"6d7bc8c7.0a2988","type":"function","z":"fea35f7b.4a29a","name":"","func":"return {_windIn: msg.payload};","outputs":1,"noerr":0,"x":310,"y":400,"wires":[["5e913c98.17cdc4"]]},{"id":"bd07a1e1.0d84f","type":"function","z":"fea35f7b.4a29a","name":"","func":"return {_windOut: msg.payload};","outputs":1,"noerr":0,"x":310,"y":460,"wires":[["5e913c98.17cdc4"]]},{"id":"46c89a41.6e00f4","type":"ui_text","z":"fea35f7b.4a29a","group":"d6caabb5.09a3d8","order":4,"width":0,"height":0,"name":"","label":"Winding Status","format":"{{msg.payload}}","layout":"row-spread","x":940,"y":400,"wires":[]},{"id":"5e913c98.17cdc4","type":"function","z":"fea35f7b.4a29a","name":"","func":"if (typeof msg._windIn === \"boolean\") context.set(\"_windIn\", msg._windIn);\nif (typeof msg._windOut === \"boolean\") context.set(\"_windOut\", msg._windOut);\n\nif (context.get(\"_windIn\") && !context.get(\"_windOut\")) return {payload: \"IN\"};\nif (!context.get(\"_windIn\") && context.get(\"_windOut\")) return {payload: \"OUT\"};\nif (!context.get(\"_windIn\") && !context.get(\"_windOut\")) return {payload: \"Stanby\"};\nif (context.get(\"_windIn\") && context.get(\"_windOut\")) return {payload: \"ERROR\"};","outputs":1,"noerr":0,"x":590,"y":400,"wires":[["46c89a41.6e00f4"]]},{"id":"220e4c49.a59074","type":"ui_switch","z":"fa3843f7.68d0b","name":"","label":"Enable Simulation","tooltip":"","group":"8b42adca.7805b","order":0,"width":0,"height":0,"passthru":true,"decouple":"false","topic":"","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","x":490,"y":60,"wires":[["3b41a772.5b3ce8"]]},{"id":"b833543c.1807d8","type":"inject","z":"fa3843f7.68d0b","name":"","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":110,"y":60,"wires":[["220e4c49.a59074"]]},{"id":"d7009b84.4c4f08","type":"function","z":"fa3843f7.68d0b","name":"Send 2","func":"if (msg.payload === true) return {payload: 2};","outputs":1,"noerr":0,"x":120,"y":420,"wires":[["736e5151.471c1"]]},{"id":"654ccd71.41c774","type":"function","z":"fa3843f7.68d0b","name":"Send 0","func":"if (msg.payload === true) return {payload: 0};","outputs":1,"noerr":0,"x":120,"y":540,"wires":[["9d1d94f5.e5cb58"]]},{"id":"3b41a772.5b3ce8","type":"function","z":"fa3843f7.68d0b","name":"Send 2","func":"if (msg.payload === true) return [{payload: true}, null];\nelse if (msg.payload === false) return [null, {payload: false}];","outputs":2,"noerr":0,"x":700,"y":60,"wires":[["d7009b84.4c4f08","654ccd71.41c774"],["98c654e4.cb8bd8","f47ce61d.32f088","1faa0141.05bbef","f0ce2063.e9039","8d2c03b.a6113","3454c985.bd8ea6","e6623556.5d0708","f08aab81.574178","17281267.c1b39e","d349fdb.ba81"]]},{"id":"d349fdb.ba81","type":"link out","z":"fa3843f7.68d0b","name":"","links":["1c713ae3.111985"],"x":995,"y":60,"wires":[]},{"id":"1c713ae3.111985","type":"link in","z":"fea35f7b.4a29a","name":"","links":["d349fdb.ba81"],"x":715,"y":320,"wires":[["39433b28.779c94","5e4bb611.72e6b8"]]}]

Flow Info

Created 4 years, 7 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • function (x13)
  • inject (x3)
  • link in (x1)
  • link out (x1)
Other
  • eth-ip endpoint (x1)
  • eth-ip in (x4)
  • eth-ip out (x12)
  • tab (x2)
  • ui_base (x1)
  • ui_dropdown (x1)
  • ui_group (x3)
  • ui_slider (x1)
  • ui_switch (x2)
  • ui_tab (x1)
  • ui_template (x3)
  • ui_text (x4)

Tags

  • PLC
  • unit
  • test
  • testing
  • Allen Bradley
  • ControlLogix
  • RSLogix
  • ethernet-ip
  • PLC program
  • IO Simulation
  • IO
  • Simulation
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option