├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── ads-connection.html ├── ads-connection.js ├── ads-helpers.js ├── ads-in.html ├── ads-in.js ├── ads-notification.html ├── ads-notification.js ├── ads-out.html ├── ads-out.js ├── ads-symbols.html ├── ads-symbols.js ├── ads-system.html ├── ads-system.js ├── icons ├── ic_syst.gif ├── ic_syst.png └── tcat.png ├── locales └── en-US │ ├── ads-connection.json │ ├── ads-in.json │ ├── ads-notification.json │ ├── ads-out.json │ ├── ads-symbols.json │ └── ads-system.json ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.1.30: Maintenance Release 2 | 3 | **Fixes** 4 | - Improvement for check ads netID 5 | 6 | ### 1.1.29: Maintenance Release 7 | 8 | **Fixes** 9 | - Improvement for connect, disconnect and deploy 10 | 11 | ### 1.1.28: Maintenance Release 12 | 13 | **Enhancements** 14 | - read/write/notification: ADS Read to indexOffset and indexGroup #33 15 | 16 | ### 1.1.27: Maintenance Release 17 | 18 | **Fixes** 19 | - Target & source port swapped in read.me #37 20 | - API (Twincat) not longer in the connection class, now it is global 21 | - Improves reconnect and lost network 22 | 23 | ### 1.1.26: Maintenance Release 24 | 25 | **Fixes** 26 | - some string.length behavior in v14.13.1 symbols and types crashes 27 | 28 | ### 1.1.25: Maintenance Release 29 | 30 | **Fixes** 31 | - some trouble on deploy 32 | 33 | ### 1.1.24: Maintenance Release 34 | 35 | **Enhancements** 36 | - add read arrays on in node and notification node 37 | - enter the client's IP address if there is more than one 38 | 39 | **Fixes** 40 | - some trouble on data error and end the socket 41 | - some trouble on restart after data error 42 | 43 | ### 1.1.23: Maintenance Release 44 | 45 | **Enhancements** 46 | 47 | - something about issue 48 | - read/write/notification: on error debug points to the correct node and varame in debug message 49 | 50 | ### 1.1.22: Maintenance Release 51 | 52 | **Fixes** 53 | 54 | - String output: The "size" argument must be of type number. Received type string #19 55 | 56 | ### 1.1.21: Maintenance Release 57 | 58 | **Fixes** 59 | 60 | - ADS notification not working after PLC has been offline (same as #14) #18 61 | 62 | ### 1.1.20: Maintenance Release 63 | 64 | **Enhancements** 65 | 66 | - ads-connection: adjustable timeout 67 | 68 | **Fixes** 69 | 70 | - node-ads-api: version 1.4.13 used 71 | - ads-out: check for undefined 72 | 73 | ### 1.1.19: Maintenance Release 74 | 75 | **Fixes** 76 | 77 | - node-ads-api: version 1.4.11 used 78 | 79 | ### 1.1.18: Maintenance Release 80 | 81 | **Fixes** 82 | 83 | - node-ads-api: version 1.4.10 used 84 | 85 | ### 1.1.17: Maintenance Release 86 | 87 | **Fixes** 88 | 89 | - node-ads-api: version 1.4.9 used 90 | 91 | 92 | ### 1.1.16: Maintenance Release 93 | 94 | **Fixes** 95 | 96 | - ads out, ads in: override intern config with msg.config: corrected varType and timezone 97 | 98 | 99 | ### 1.1.15: Maintenance Release 100 | 101 | **Enhancements** 102 | 103 | - issue #11 multiple notifications with the same variable 104 | - ads out, ads in: override intern config with msg.config 105 | 106 | **Fixes** 107 | 108 | - Release ADS-NOTIFICATION on deploy modified Node / modified Flows 109 | - PLC relieved, live sign increased to 10 sec. 110 | 111 | 112 | ### 1.1.14: Maintenance Release 113 | 114 | **Enhancements** 115 | 116 | - Cache datatyps and symbols 117 | - Topic added for all nodes. 118 | - If a topic is entered at ADS-OUT, the value is only sent if the topic is the same. 119 | 120 | 121 | **Fixes** 122 | 123 | - Exception when use Time or Date on ADS-NOTIFICATION 124 | 125 | 126 | ### 1.1.13: Maintenance Release 127 | 128 | **Enhancements** 129 | 130 | - debug added 131 | ``` 132 | debug=node-red-contrib-ads:* 133 | debug=node-red-contrib-ads:*,-node-red-contrib-ads:adsConnectionNode:Cyclic 134 | debug=node-red-contrib-ads:adsConnectionNode 135 | debug=node-red-contrib-ads:adsConnectionNode:Cyclic 136 | debug=node-red-contrib-ads:adsNotificationNode 137 | debug=node-red-contrib-ads:adsInNode 138 | debug=node-red-contrib-ads:adsOutNode 139 | debug=node-red-contrib-ads:adsSystemNode 140 | debug=node-red-contrib-ads:adsSymbolsNode 141 | ``` 142 | 143 | 144 | **Fixes** 145 | 146 | - ADS ads-symbols on TC3 147 | - [Node.js under heavy load when connection down #6](https://github.com/PLCHome/node-red-contrib-ads/issues/6) 148 | - node-ads-api 1.4.7: Notification result may be empty when the connection is closed 149 | 150 | 151 | ### 1.1.12: Maintenance Release 152 | 153 | **Enhancements** 154 | 155 | - Example for web ui with Node for get symbols and types from ADS. 156 | 157 | 158 | **Fixes** 159 | 160 | - ADS Notification Note wont work 161 | 162 | 163 | ### 1.1.11: Maintenance Release 164 | 165 | **Enhancements** 166 | 167 | - Node for get symbols and types from ADS 168 | - Nodes show Varname instead of Node-name 169 | - revision README.md 170 | - Use input msg for output in ADS In 171 | 172 | 173 | **Fixes** 174 | 175 | - README.md wrong link CHANGELOG.md 176 | 177 | 178 | ### 1.1.10: Maintenance Release 179 | 180 | **Enhancements** 181 | 182 | - Revision README.md 183 | - Message property for ADS-OUT/ADS-NOTIFICATION 184 | 185 | 186 | **Fixes** 187 | 188 | - Node red does not start if the port number is too large 189 | - System-state on timer 50 ms decoupled so that it sends less frequently 190 | - Set system-state to INVALID, when connect-state not RUN 191 | - Exception by get a message property on ADS-IN 192 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chris Traeger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # I am looking for a contributor to this repository. I don't need this node anymore and didn't want to develop it any more. However, I can host it myself, so save yourself the inquiries whether you can host it! 2 | 3 | # This component has some problems if the network connection to the PLC is interrupted, the PLC is not running, incorrect data is stored for the PLC or there is a deployment in the node red. 4 | Everyone can take care of the problems, eliminate them and make a request. 5 | But let that cry in the issues. 6 | 7 | # node-red-contrib-ads 8 | 9 | > A Node-RED implementation for the Twincat ADS protocol. 10 | > (Twincat and ADS provided by Beckhoff ©. I'm not affiliated.) 11 | 12 | Beckhoff TwinCat ADS support for Node-Red. Provides nodes to talk to TwinCat PLC variables over ADS. 13 | 14 | Uses the NODE-ADS library for Node.JS (https://www.npmjs.com/package/node-ads). 15 | 16 | It's tested with TwinCat 2 but its also works with TwinCat 3. Look at the Port. 17 | 18 | For the latest updates see the [CHANGELOG.md](https://github.com/PLCHome/node-red-contrib-ads/blob/master/CHANGELOG.md) 19 | 20 | ## issue 21 | 22 | If you have problems do not be afraid to open an issue. 23 | 24 | If you open a new issue please remember that I do not know your hardware, software, PLC and programming. 25 | 26 | Therefore it would be nice if you answer the following questions: 27 | 28 | ``` 29 | node-red-contrib-ads version: 30 | nodered version: 31 | on which operating system is the Node-RED running: 32 | on which hardware system is the Nodered running: (PC/Raspberry) 33 | PLC type: 34 | Twincat version of the PLC: 35 | Twincat version of the development environment: 36 | ``` 37 | 38 | ## Installation 39 | 40 | ```sh 41 | cd ~/.node-red 42 | npm install node-red-contrib-ads 43 | ``` 44 | 45 | ## Requirements 46 | * Beckhoff PLC that has an ethernet connection and is connected to your LAN 47 | * Make your you give the PLC a fixed IP address 48 | * Make sure you can ping the PLC from another computer 49 | 50 | ## Configuration 51 | 52 | 1. Enable ADS on your PLC project. To do this click on your task and then enable the checkbox before `Create symbols` (if he is not disabled). 53 | In addition, you can still, under I/O Devices click on Image and go to the ADS tab. Check the `Enable ADS Server` and also `Create symbols`. 54 | Download the new configuration and make sure you reboot your PLC. The reboot is only needed when you are using TwinCat 2. 55 | 56 | 2. Now add a static route to our Beckhoff PLC. The route should point to your server that will run the proxy application. 57 | It's also a good idea to add an extra static route that points to your local development device. This way you can test out the proxy from your development device too. 58 | 59 | ### Attention 60 | 61 | 1. TwinCAT AMS Router doesn't allow multiple TCP connections from the same host. So when you use two AdsLib instances on the same host to connect to the same TwinCAT router, you will see that TwinCAT will close the first TCP connection and only respond to the newest. If you start the TwinCat System Manager and Node-Red ADS on the same PC at the same time, Node-Red will not run anymore. You can set up a second IPv4 on the PC and assign to this a ADS NET ID under Twincat 62 | 63 | 2. As ADS is transmitted over a TCP connection, there is no real time guarantee. 64 | 65 | 66 | ## Global variables 67 | Global variables must start with a dot: ```.engine``` 68 | ``` 69 | VAR_GLOBAL 70 | engine AT %QX0.0: BOOL; 71 | deviceUp AT %QX0.1: BOOL; 72 | deviceDown AT %QX0.2: BOOL; 73 | timerUp: TON; 74 | timerDown: TON; 75 | steps: BYTE; 76 | count: UINT := 0; 77 | devSpeed: TIME := t#10ms; 78 | devTimer: TON; 79 | switch: BOOL; 80 | END_VAR 81 | ``` 82 | 83 | Program variables must start with the programname: ```MAIN.UpTyp.timerUp.PT``` 84 | 85 | 86 | 87 | ## Nodes added by this package 88 | 89 | #### - ads-connection 90 | 91 | A node that represents a TwinCat ADS device. 92 | Be sure to create one for an PLC. 93 | 94 | ``` 95 | Host: IP address of the PLC 96 | Client: IP address of the client if there is more than one. If it is empty, it will be determined automatically. 97 | Target NetId: ADS NET ID of the PLC in the format 192.168.2.5.1.1 98 | Source NetId: ADS NET ID for node red in the format 192.168.2.10.1.1 the same as the one added to the static route in the Beckhoff PLC. 99 | Port: Normally 48898 for TwinCat 2/3 100 | Target Port: Normally 801 for TwinCat 2 Runtime 1 or 851 for TwinCat 3 Runtime 1 101 | Source Port: Normally 32905 for TwinCat 2/3 102 | Timeout ms: The timeout in milliseconds, by default 500 103 | ``` 104 | 105 | #### - ADS System 106 | 107 | Twincat ADS node that give you information about the PLC and PLC state. 108 | 109 | ##### input 110 | You can write anything on the input, then the system state is pushed to the output. 111 | 112 | ##### output 113 | ``` 114 | msg.payload : Object 115 | { "connectState":2, 116 | "connectStateText":"CONNECTED", 117 | "adsState":5, 118 | "adsStateText":"RUN", 119 | "majorVersion":2, 120 | "minorVersion":11, 121 | "versionBuild":2605, 122 | "version":"2.11.2605", 123 | "deviceName":"TCatPlcCtrl", 124 | "symTab":138 125 | } 126 | ``` 127 | ###### Connect state (text) 128 | The following connect state values are possible: 129 | ``` 130 | ERROR: -1 131 | DISCONNECTED: 0 132 | CONNECTING: 1 133 | CONNECTED: 2 134 | DISCONNECTING: 3 135 | ``` 136 | 137 | ###### ADS state (text) 138 | The following ADS state values are possible: 139 | ``` 140 | INVALID: 0 141 | IDLE: 1 142 | RESET: 2 143 | INIT: 3 144 | START: 4 145 | RUN: 5 146 | STOP: 6 147 | SAVECFG: 7 148 | LOADCFG: 8 149 | POWERFAILURE: 9 150 | POWERGOOD: 10 151 | ERROR: 11 152 | SHUTDOWN: 12 153 | SUSPEND: 13 154 | RESUME: 14 155 | CONFIG: 15 156 | RECONFIG: 16 157 | STOPPING: 17 158 | ``` 159 | 160 | ###### Version 161 | This is the version of the PLC runtime. Either in single parts ("majorVersion", "minorVersion", "versionBuild") or assembled ("version") 162 | 163 | ###### Devicename 164 | The Name of the PLC-Device 165 | 166 | ###### symTab 167 | The PLC internal version number of the variable Händel assignment 168 | 169 | 170 | #### - ADS Out 171 | 172 | Twincat ADS output node that can send values to the PLC. 173 | 174 | Enter the name of the variable, the type and the property name. If ads is connected the value is written to the PLC 175 | 176 | If a topic is entered, the value is only sent if the topic is the same. 177 | 178 | There is an possible to override the node configuration. You can add an property config to the message object. Not all configuration properties need to be overridden. 179 | - `config.useIndex` : (boolen) override the variable use ID (true is not recommended for Twincat) 180 | - `config.varName`: (string) override the variable name 181 | - `config.indexGroup`: (interger/hex-string) override the variable indexGroup only used by useIndex varName is unused then 182 | - `config.indexOffset`: (interger/hex-string) override the variable indexOffset only used by useIndex varName is unused then 183 | - `config.varType`: (string) override the variable type 184 | - `config.varSize`: (integer) the length on RAW and STRING type 185 | - `config.timezone`: (string) only on date and time type 'TO_LOCAL' or 'UNCHANGED' 186 | - `config.outProperty`: (string) the property for the outvalue 187 | - `config.topic`: (string) the topic being checked against the message topic. It can be '' to delete an topic. 188 | 189 | 190 | #### - ADS In 191 | 192 | Twincat ADS input node that can recive values from the PLC. 193 | 194 | This note is not for the transmission of cyclic data. Do not use it in conjunction with a timer. There is a notification node for this. 195 | 196 | Enter the name of the variable, the type and the property name for the output. 197 | You can still decide whether a new output with the property for the value will be created or the property will be inserted into the inputvalue and output at the output. 198 | 199 | There is an possible to override the node configuration. You can add an property config to the message object. Not all configuration properties need to be overridden. 200 | - `config.useIndex` : (boolen) override the variable use ID (true is not recommended for Twincat) 201 | - `config.varName`: (string) override the variable name 202 | - `config.indexGroup`: (integer/hex-string) override the variable indexGroup only used by useIndex varName is unused then 203 | - `config.indexOffset`: (integer/hex-string) override the variable indexOffset only used by useIndex varName is unused then 204 | - `config.varType`: (string) override the variable type 205 | - `config.varSize`: (integer) the length on RAW and STRING type 206 | - `config.isarray`: (bool) is an ARRAY type, varLowIndex and varHighIndex must set 207 | - `config.varLowIndex`: (integer) the Low index on ARRAY type 208 | - `config.varHighIndex`: (integer) the High index on ARRAY type 209 | - `config.timezone`: (string) only on date and time type 'TO_LOCAL' or 'UNCHANGED' 210 | - `config.inProperty`: (string) the property for the outvalue 211 | - `config.useInputMsg`: (bool) the input message will be used to build the out message or an new message will be build. 212 | - `config.topic`: (string) the topic being checked against the message topic. It can be '' to delete an topic. 213 | 214 | #### - ADS Notification 215 | 216 | Twincat ADS input node that can automatically recive values from the PLC, if they change. 217 | Beckhoff says you should not have more than 510 variables monitored. 218 | Each time the ADS-Node is connected to the PLC, the PLC automatically sends the value once. 219 | 220 | Note, if you have multiple Notification with the same variable name then only one of the nodes will be registered in the PLC. Which is coincidence. therefore, the same variable may not be "cyclic" and "onchange" at the same time. Or number and bool at the same time. Please use the link node from the node red base nodes. 221 | 222 | - Transmission Mode: With "cyclic", the variable is polled cyclically by the PLC and transmitted, with "onchange" the PLC monitors. 223 | - Max Delay: At the latest after this time, the ADS Device Notification is called. The unit is 1ms. 224 | - Cycle Time: The ADS server checks if the value changes in this time slice. The unit is 1ms 225 | 226 | 227 | #### - ADS Symbols 228 | 229 | This note loads a list of all symbols or types from the PLC when written to the input. If a string or string array is written, the output is filtered by name. Wildcards [?=char;*=chars] are possible. 230 | 231 | This example makes an web ui for inspecting the PLC. The ADS Connection still needs to be set. 232 | ```json 233 | [{"id":"b0a9c705.017d08","type":"ADS Symbols","z":"524f011a.f0bce8","name":"","datasource":"","data":"SYMBOLES","x":370,"y":940,"wires":[["f9278f67.b8ba2"]]},{"id":"f9278f67.b8ba2","type":"ui_template","z":"524f011a.f0bce8","group":"aa9409af.67136","name":"","order":0,"width":"26","height":"6","format":"\n\n
\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Index GroupIndex OffsetNameTypeComment
{{ obj.indexGroup }}{{ obj.indexOffset }}{{ obj.name }}{{ obj.type }}{{ obj.comment }}
\n
","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":600,"y":940,"wires":[[]]},{"id":"cf2f5485.8f3268","type":"ADS Symbols","z":"524f011a.f0bce8","name":"","datasource":"","data":"TYPES","x":360,"y":1020,"wires":[["803b7b42.ce51b"]]},{"id":"803b7b42.ce51b","type":"ui_template","z":"524f011a.f0bce8","group":"f880e964.b5012","name":"","order":2,"width":"26","height":"10","format":"\n\n
\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
VersionSizeData TypeNameTypeCommentArray DimSub Types
{{ obj.version }}{{ obj.size }}{{ obj.dataType }}{{ obj.name }}{{ obj.type }}{{ obj.comment }}\n \n \n \n \n \n \n \n \n \n
Low BoundElements
{{ sobj.lBound }}{{ sobj.elements }}
\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
IndexVersionSizeData TypeOffsetNameTypeComment
{{ sobj.index }}{{ sobj.version }}{{ sobj.size }}{{ sobj.dataType }}{{ sobj.offs }}{{ sobj.name }}{{ sobj.type }}{{ sobj.comment }}
\n
\n
","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":600,"y":1020,"wires":[[]]},{"id":"6dadd36d.9d2334","type":"ui_text_input","z":"524f011a.f0bce8","name":"","label":"Search","group":"f880e964.b5012","order":1,"width":0,"height":0,"passthru":false,"mode":"text","delay":"0","topic":"","x":140,"y":1020,"wires":[["cf2f5485.8f3268"]]},{"id":"8dfe926d.20cbb","type":"ui_text_input","z":"524f011a.f0bce8","name":"","label":"Search","group":"aa9409af.67136","order":1,"width":"0","height":"0","passthru":false,"mode":"text","delay":"0","topic":"","x":140,"y":940,"wires":[["b0a9c705.017d08"]]},{"id":"aa9409af.67136","type":"ui_group","z":"","name":"Symboles","tab":"65f5295f.68b3c","order":1,"disp":true,"width":"26","collapse":true},{"id":"f880e964.b5012","type":"ui_group","z":"","name":"Types","tab":"65f5295f.68b3c","order":2,"disp":true,"width":"26","collapse":true},{"id":"65f5295f.68b3c","type":"ui_tab","z":"","name":"inspect PLC","icon":"fingerprint","order":2}] 234 | ``` 235 | 236 | 237 | This example makes an file for excel. The ADS Connection still needs to be set. 238 | ```json 239 | [{"id":"2cf4e73.bcb4f98","type":"ADS Symbols","z":"524f011a.f0bce8","name":"","datasource":"","data":"SYMBOLES","x":300,"y":740,"wires":[["9fd1a1c7.a10a6"]]},{"id":"9fd1a1c7.a10a6","type":"csv","z":"524f011a.f0bce8","name":"","sep":"\\t","hdrin":false,"hdrout":true,"multi":"one","ret":"\\r\\n","temp":"indexGroup,indexOffset,size,name,type,comment","skip":"0","x":470,"y":740,"wires":[["3f33ed7f.08d41a"]]},{"id":"3f33ed7f.08d41a","type":"file","z":"524f011a.f0bce8","name":"","filename":"symboles.xls","appendNewline":false,"createDir":false,"overwriteFile":"true","x":690,"y":740,"wires":[[]]},{"id":"cf639909.782c38","type":"inject","z":"524f011a.f0bce8","name":"all","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":740,"wires":[["2cf4e73.bcb4f98"]]},{"id":"bb6e0397.998ca8","type":"ADS Symbols","z":"524f011a.f0bce8","name":"","datasource":"","data":"TYPES","x":300,"y":800,"wires":[["df532662.17485"]]},{"id":"afa626ac.36da48","type":"csv","z":"524f011a.f0bce8","name":"","sep":"\\t","hdrin":false,"hdrout":true,"multi":"one","ret":"\\r\\n","temp":"version,size,dataType,subItems,name,type,lBound,elements,entryIndex,entryVersion,entrySize,entryOffs,entryDataType,entryName,entryType","skip":"0","x":670,"y":800,"wires":[["ace69011.4a4f18"]]},{"id":"ace69011.4a4f18","type":"file","z":"524f011a.f0bce8","name":"","filename":"types.xls","appendNewline":false,"createDir":false,"overwriteFile":"true","x":840,"y":800,"wires":[[]]},{"id":"12d3e5af.381b3a","type":"inject","z":"524f011a.f0bce8","name":"search","topic":"","payload":"[\"typeName1*\",\"type?ame2*\"]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":800,"wires":[["bb6e0397.998ca8"]]},{"id":"df532662.17485","type":"function","z":"524f011a.f0bce8","name":"format for csv","func":"var outmsg = {payload:[]}\nif (Array.isArray(msg.payload)) {\n msg.payload.map(function (data){\n out = {\n version: data.version,\n hashValue: data.hashValue,\n typeHashValue: data.typeHashValue,\n size: data.size,\n dataType: data.dataType,\n subItems: data.subItems,\n name: data.name,\n type: data.type,\n arrayDim: data.arrayDim,\n comment: data.comment\n }\n outmsg.payload.push(out)\n \n if (data.array) {\n data.array.map(function(adata) {\n adata.name= data.name\n outmsg.payload.push(adata)\n }) \n }\n\n if (data.datatyps) {\n data.datatyps.map(function(sdata) {\n out = {\n name: data.name,\n entryIndex: sdata.index,\n entryVersion: sdata.version,\n entryHashValue: sdata.hashValue,\n entryTypeHashValue: sdata.typeHashValue,\n entrySize: sdata.size,\n entryOffs: sdata.offs,\n entryDataType: sdata.dataType,\n entryName: sdata.name,\n entryType: sdata.type,\n entryArrayDim: data.arrayDim,\n entryComment: sdata.comment,\n array: sdata.array\n }\n outmsg.payload.push(out)\n \n }) \n }\n })\n}\nreturn outmsg","outputs":1,"noerr":0,"x":500,"y":800,"wires":[["afa626ac.36da48"]]},{"id":"fa01d720.55c0e8","type":"inject","z":"524f011a.f0bce8","name":"all","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":840,"wires":[["bb6e0397.998ca8"]]},{"id":"b16a18f7.e0ece8","type":"inject","z":"524f011a.f0bce8","name":"search","topic":"","payload":"[\".var1*\",\".?ar2*\"]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":700,"wires":[["2cf4e73.bcb4f98"]]}] 240 | ``` 241 | 242 | License (MIT) 243 | ------------- 244 | Copyright (c) 2018 Chris Traeger 245 | 246 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 247 | 248 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 249 | 250 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 251 | -------------------------------------------------------------------------------- /ads-connection.html: -------------------------------------------------------------------------------- 1 | 35 | 36 | -------------------------------------------------------------------------------- /ads-connection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var nodeads = require('node-ads-api') 3 | var adsHelpers = require('./ads-helpers') 4 | var util = require('util') 5 | var debug = require('debug')('node-red-contrib-ads:adsConnectionNode') 6 | var debugCyclic = require('debug')('node-red-contrib-ads:adsConnectionNode:Cyclic') 7 | var twincat={} 8 | 9 | module.exports = function (RED) { 10 | 11 | function adsConnectionNode(config) { 12 | RED.nodes.createNode(this, config) 13 | var node = this 14 | debug('config:',config) 15 | 16 | node.system = {} 17 | internalSetConnectState(adsHelpers.connectState.DISCONNECTED) 18 | internalSetAdsState(nodeads.ADSSTATE.INVALID) 19 | 20 | node.notificationNodes = [] 21 | node.notificationSubscribed = {} 22 | node.systemNodes = [] 23 | 24 | function removeClient(){ 25 | debug('removeClient:','Client:',!(!twincat.adsClient),'symbolsCache:',!(!twincat.symbolsCache),'datatypsCache:',!(!twincat.datatypsCache)) 26 | node.notificationSubscribed = {} 27 | delete(twincat.adsClient) 28 | delete(twincat.symbolsCache) 29 | delete(twincat.datatypsCache) 30 | } 31 | 32 | function callbackSafeIfStillConnected(adsClient, cb) { 33 | return function() { 34 | if (twincat.adsClient !== adsClient) { 35 | return; 36 | } 37 | 38 | try { 39 | return cb(...arguments); 40 | } catch (ex) { 41 | node.error(ex); 42 | } 43 | }; 44 | }; 45 | 46 | /* connect to PLC */ 47 | function connect() { 48 | internalSetConnectState(adsHelpers.connectState.CONNECTING) 49 | startTimer(45000) 50 | var adsoptions = {} 51 | 52 | adsoptions.host = config.host 53 | if (config.clientIP !== "") { 54 | adsoptions.localAddress = config.clientIP 55 | } 56 | adsoptions.amsNetIdTarget = config.amsNetIdTarget 57 | adsoptions.amsNetIdSource = config.amsNetIdSource 58 | adsoptions.port = parseInt(config.port) 59 | adsHelpers.checkPort(node,adsoptions.port,48898) 60 | adsoptions.amsPortSource = parseInt(config.amsPortSource) 61 | adsHelpers.checkPort(node,adsoptions.amsPortSource,801) 62 | adsoptions.amsPortTarget = parseInt(config.amsPortTarget) 63 | adsHelpers.checkPort(node,adsoptions.amsPortTarget,32905) 64 | adsoptions.timeout = parseInt(config.adsTimeout||500) 65 | if (adsoptions.timeout < 500) adsoptions.timeout = 500 66 | debug('connect:',adsoptions) 67 | if (twincat.adsClient) { 68 | startTimer(5000) 69 | } else { 70 | twincat.adsClient = nodeads.connect(adsoptions, 71 | function (){ 72 | twincat.adsClient.readDeviceInfo(callbackSafeIfStillConnected(twincat.adsClient, function (err,handel) { 73 | if (err) { 74 | removeClient() 75 | node.error('Error on connect: check target NetId or routing') 76 | internalSetConnectState(adsHelpers.connectState.ERROR) 77 | startTimer(120000) 78 | debug('connect:','readDeviceInfo:',err) 79 | } else { 80 | debug('connect:','readDeviceInfo:',handel) 81 | node.system.majorVersion = handel.majorVersion 82 | node.system.minorVersion = handel.minorVersion 83 | node.system.versionBuild = handel.versionBuild 84 | node.system.version = handel.majorVersion+'.'+handel.minorVersion+'.'+handel.versionBuild 85 | node.system.deviceName = handel.deviceName 86 | internalSystemUpdate() 87 | internalSubscribeLiveTick() 88 | startTimer(120000) 89 | internalSubscribeSymTab() 90 | function subscribe(i,cb){ 91 | if (i{subscribe(++i,cb)}) 93 | } else cb() 94 | } 95 | subscribe(0,()=>{internalSetConnectState(adsHelpers.connectState.CONNECTED)}) 96 | //node.notificationNodes.forEach(internalSubscribe) 97 | internalSetConnectState(adsHelpers.connectState.CONNECTED) 98 | } 99 | })) 100 | } 101 | ) 102 | } 103 | 104 | twincat.adsClient.on('notification', callbackSafeIfStillConnected(twincat.adsClient, function (handle){ 105 | if (handle.indexGroup == nodeads.ADSIGRP.DEVICE_DATA && 106 | handle.indexOffset == nodeads.ADSIOFFS_DEVDATA.ADSSTATE) { 107 | debugCyclic('notification:','connectCheckADSSTATE:', handle.indexGroup, handle.indexGroup, 108 | 'handle.indexOffset', handle.indexOffset, 'handle.value', handle.value) 109 | if (node.system.connectState == adsHelpers.connectState.CONNECTED) { 110 | startTimer(10000) 111 | } 112 | internalSetAdsState(handle.value) 113 | } else if (handle.indexGroup == nodeads.ADSIGRP.SYM_VERSION && 114 | handle.indexOffset == 0) { 115 | debug('notification:','CheckSYM_VERSION:', 'handle.indexGroup:', handle.indexGroup, 116 | 'handle.indexOffset', handle.indexOffset, 'handle.value', handle.value) 117 | if (node.system.symTab != handle.value) { 118 | node.system.symTab = handle.value 119 | internalSystemUpdate() 120 | } 121 | if (handle.start) { 122 | handle.start = false 123 | } else { 124 | node.log(util.format('new sym Version - restart')) 125 | internalRestart(connect) 126 | } 127 | } 128 | if (node.notificationSubscribed[handle.symname]) { 129 | debug('notification:',handle.symname, 'handle.notifyHandle', handle.notifyHandle, 130 | 'handle.totalByteLength', handle.totalByteLength, 'handle.symhandle', handle.symhandle, 'handle.value', handle.value, 131 | 'handle.bytelength', handle.bytelength) 132 | node.notificationSubscribed[handle.symname].map((n)=> { 133 | if (n.onAdsData) { 134 | n.onAdsData(handle) 135 | } 136 | }) 137 | } 138 | } 139 | )) 140 | 141 | 142 | twincat.adsClient.on('error', callbackSafeIfStillConnected(twincat.adsClient, function (error) { 143 | debug('onerror:',error) 144 | if (error){ 145 | node.error(util.format('Error ADS: %s', error)) 146 | if (!twincat.connected) { 147 | if (node.system.connectState == adsHelpers.connectState.CONNECTING) { 148 | internalSetConnectState(adsHelpers.connectState.ERROR) 149 | } 150 | internalRestart(() => {startTimer(60000)}) 151 | } 152 | } 153 | } 154 | )) 155 | 156 | } 157 | /* end connect to PLC */ 158 | 159 | /* check connection to PLC */ 160 | var conncetTimer 161 | 162 | function startTimer(time){ 163 | const defTime = 60000 164 | debugCyclic('startTimer:','clearTimeout') 165 | clearTimeout(conncetTimer) 166 | if (node.system.connectState != adsHelpers.connectState.DISCONNECTING) { 167 | debugCyclic('startTimer:',time||defTime) 168 | conncetTimer = setTimeout(function () { 169 | debug('startTimer:','Timeout') 170 | if (node.system.connectState == adsHelpers.connectState.CONNECTED) { 171 | internalSetConnectState(adsHelpers.connectState.DISCONNECTED) 172 | } 173 | internalRestart(connect) 174 | },time||defTime) 175 | } 176 | } 177 | /* needs internalSubscribeLiveTick */ 178 | /* end check connection to PLC */ 179 | 180 | /* for ads-notification */ 181 | node.subscribe = function (n) { 182 | debug('subscribe:',n.id, n.type) 183 | if (node.notificationNodes.indexOf(n) < 0) { 184 | node.notificationNodes.push(n) 185 | } 186 | if (node.system.connectState == adsHelpers.connectState.CONNECTED) { 187 | internalSubscribe(n) 188 | } 189 | } 190 | 191 | node.unsubscribe = function (n,cb) { 192 | debug('unsubscribe:',n.id, n.type, n.notifyHandle) 193 | var index = node.notificationNodes.indexOf(n) 194 | if (index >= 0) { 195 | node.notificationNodes.splice(index,1) 196 | } 197 | if ((node.notificationSubscribed[n.symname])){ 198 | var index = node.notificationSubscribed[n.symname].indexOf(n) 199 | if (index >= 0) { 200 | node.notificationSubscribed[n.symname].splice(index,1) 201 | } 202 | if (!node.notificationSubscribed[n.symname].length){ 203 | delete node.notificationSubscribed[n.symname] 204 | } 205 | } 206 | if (n.notifyHandle !== undefined && (!node.notificationSubscribed[n.symname])) { 207 | var handle = { 208 | notifyHandle: n.notifyHandle 209 | } 210 | if (twincat.adsClient) { 211 | twincat.adsClient.releaseNotificationHandle(handle, function() { 212 | delete(n.notifyHandle) 213 | debug('unsubscribe: done') 214 | if (cb){ 215 | cb() 216 | } 217 | }) 218 | } else { 219 | debug('unsubscribe: twincat.adsClient deleted') 220 | if (cb){ 221 | cb() 222 | } 223 | } 224 | } else { 225 | debug('unsubscribe: cat release handle:',notifyHandle) 226 | if (cb){ 227 | cb() 228 | } 229 | } 230 | } 231 | 232 | /* end for ads-notification */ 233 | 234 | /* subscribe on PLC */ 235 | function internalSubscribe(n,cb){ 236 | debug('internalSubscribe:',n.id, n.type) 237 | var handle = { 238 | transmissionMode: nodeads.NOTIFY[n.transmissionMode], 239 | maxDelay: n.maxDelay, 240 | cycleTime: n.cycleTime, 241 | } 242 | if (n.useIndex) { 243 | handle.indexGroup = n.indexGroup 244 | handle.indexOffset = n.indexOffset 245 | } else { 246 | handle.symname = n.symname 247 | } 248 | if (adsHelpers.isRawType(n.adstype)) { 249 | handle.bytelength = parseInt(n.bytelength) 250 | } else { 251 | if (n.array) { 252 | if (adsHelpers.isStringType(n.adstype) && n.bytelength) { 253 | handle.bytelength = nodeads.array(nodeads.string(parseInt(n.bytelength)),parseInt(n.lowindex),parseInt(n.highindex)) 254 | } else { 255 | handle.bytelength = nodeads.array(nodeads[n.adstype],parseInt(n.lowindex),parseInt(n.highindex)) 256 | } 257 | } else { 258 | if (adsHelpers.isStringType(n.adstype) && n.bytelength) { 259 | handle.bytelength = nodeads.string(parseInt(n.bytelength)) 260 | } else { 261 | handle.bytelength = nodeads[n.adstype] 262 | } 263 | } 264 | } 265 | if (adsHelpers.isTimezoneType(n.adstype)) { 266 | handle.useLocalTimezone = (n.timezone === "TO_LOCAL") 267 | } 268 | if (!(node.notificationSubscribed[n.symname])){ 269 | if (twincat.adsClient) { 270 | debug('internalSubscribe:',handle) 271 | node.notificationSubscribed[n.symname]=[] 272 | node.notificationSubscribed[n.symname].push(n) 273 | twincat.adsClient.notify(handle, callbackSafeIfStillConnected(twincat.adsClient, function(err){ 274 | if (err){ 275 | n.error(util.format("Ads Register Notification '%s' %s",n.symname, err)) 276 | } else { 277 | node.notificationSubscribed[n.symname].map((no)=>{ 278 | no.notifyHandle = handle.notifyHandle 279 | }) 280 | } 281 | if (cb){ 282 | cb() 283 | } 284 | })) 285 | } else if (cb) cb() 286 | } else { 287 | var index = node.notificationSubscribed[n.symname].push(n) 288 | debug('internalSubscribe: reuse notifyHandle',node.notificationSubscribed[n.symname][0].notifyHandle) 289 | if (node.notificationSubscribed[n.symname][0].notifyHandle) { 290 | node.notificationSubscribed[n.symname].map((n)=>{ 291 | if (!n.notifyHandle) { 292 | n.notifyHandle = node.notificationSubscribed[n.symname][0].notifyHandle 293 | } 294 | }) 295 | } 296 | if (cb) cb() 297 | } 298 | } 299 | 300 | function internalSubscribeLiveTick(){ 301 | debug('internalSubscribeLiveTick:') 302 | var handle = { 303 | indexGroup: nodeads.ADSIGRP.DEVICE_DATA, 304 | indexOffset: nodeads.ADSIOFFS_DEVDATA.ADSSTATE, 305 | transmissionMode: nodeads.NOTIFY.CYCLIC, 306 | cycleTime: 1000, 307 | bytelength: nodeads.WORD 308 | } 309 | if (twincat.adsClient) { 310 | debug('internalSubscribeLiveTick:',handle) 311 | twincat.adsClient.notify(handle, function(err){ 312 | if (err){ 313 | node.error(util.format('Ads Register Notification live tick %s', err)) 314 | } 315 | }) 316 | } 317 | } 318 | 319 | function internalSubscribeSymTab(){ 320 | debug('internalSubscribeLiveTick:') 321 | var handle = { 322 | indexGroup: nodeads.ADSIGRP.SYM_VERSION, 323 | indexOffset: 0, 324 | transmissionMode: nodeads.NOTIFY.ONCHANGE, 325 | bytelength: nodeads.BYTE, 326 | start: true 327 | } 328 | 329 | if (twincat.adsClient) { 330 | debug('internalSubscribeSymTab:',handle) 331 | twincat.adsClient.notify(handle, function(err){ 332 | if (err){ 333 | node.error(util.format('Ads Register Notification Sym Tab %s', err)) 334 | } 335 | }) 336 | } 337 | } 338 | /* end subscribe on PLC */ 339 | 340 | /* write to PLC */ 341 | node.write = function (n, config, value) { 342 | debug('write:',n.id, n.type) 343 | if (node.system.connectState == adsHelpers.connectState.CONNECTED) { 344 | var handle = { 345 | propname: 'value', 346 | value: value 347 | } 348 | if (config.useIndex) { 349 | handle.indexGroup = config.indexGroup 350 | handle.indexOffset = config.indexOffset 351 | } else { 352 | handle.symname = config.symname 353 | } 354 | if (adsHelpers.isRawType(config.adstype)) { 355 | handle.bytelength = parseInt(config.bytelength) 356 | } else { 357 | if (adsHelpers.isStringType(config.adstype) && config.bytelength) { 358 | handle.bytelength = nodeads.string(parseInt(config.bytelength)) 359 | } else { 360 | handle.bytelength = nodeads[config.adstype] 361 | } 362 | } 363 | if (adsHelpers.isTimezoneType(config.adstype)) { 364 | handle.useLocalTimezone = (config.timezone === "TO_LOCAL") 365 | } 366 | if (twincat.adsClient) { 367 | debug('write:',handle) 368 | twincat.adsClient.write(handle, 369 | function (err){ 370 | if (err) { 371 | n.error(util.format("Ads write '%s' %s", config.symname,err)) 372 | } 373 | } ) 374 | } 375 | } 376 | } 377 | /* end write to PLC */ 378 | 379 | /* read from PLC */ 380 | node.read = function (n, config, cb) { 381 | debug('read:',n.id, n.type) 382 | if (node.system.connectState == adsHelpers.connectState.CONNECTED) { 383 | var handle = { 384 | propname: 'value' 385 | } 386 | if (config.useIndex) { 387 | handle.indexGroup = config.indexGroup 388 | handle.indexOffset = config.indexOffset 389 | } else { 390 | handle.symname = config.symname 391 | } 392 | if (adsHelpers.isRawType(config.adstype)) { 393 | handle.bytelength = parseInt(config.bytelength) 394 | } else { 395 | if (config.array) { 396 | if (adsHelpers.isStringType(config.adstype) && config.bytelength) { 397 | handle.bytelength = nodeads.array(nodeads.string(parseInt(config.bytelength)),parseInt(config.lowindex),parseInt(config.highindex)) 398 | } else { 399 | handle.bytelength = nodeads.array(nodeads[config.adstype],parseInt(config.lowindex),parseInt(config.highindex)) 400 | } 401 | } else { 402 | if (adsHelpers.isStringType(config.adstype) && config.bytelength) { 403 | handle.bytelength = nodeads.string(parseInt(config.bytelength)) 404 | } else { 405 | handle.bytelength = nodeads[config.adstype] 406 | } 407 | } 408 | } 409 | if (adsHelpers.isTimezoneType(config.adstype)) { 410 | handle.useLocalTimezone = (config.timezone === "TO_LOCAL") 411 | } 412 | if (twincat.adsClient) { 413 | debug('read:',handle) 414 | twincat.adsClient.read(handle, function(err, handle){ 415 | if (err) { 416 | n.error(util.format("Ads read '%s' %s",config.symname, err)) 417 | } else { 418 | cb(handle) 419 | } 420 | }) 421 | } 422 | } 423 | } 424 | /* end read from PLC */ 425 | 426 | /* node RIP */ 427 | node.on('close', function (done) { 428 | debug('close:') 429 | internalRestart(done) 430 | }) 431 | 432 | function internalRestart(done) { 433 | debug('internalRestart:','enter') 434 | internalSetConnectState(adsHelpers.connectState.DISCONNECTING) 435 | clearTimeout(conncetTimer) 436 | if (twincat.adsClient) { 437 | let ads = twincat.adsClient 438 | delete(twincat.adsClient) 439 | ads.end(function (){ 440 | internalSetConnectState(adsHelpers.connectState.DISCONNECTED) 441 | removeClient() 442 | var sleep = setTimeout(function () { 443 | clearTimeout(sleep) 444 | debug('internalRestart:','done') 445 | done() 446 | },1000) 447 | }) 448 | } else { 449 | debug('internalRestart:','done no adsClient') 450 | internalSetConnectState(adsHelpers.connectState.DISCONNECTED) 451 | done() 452 | } 453 | } 454 | /* end node RIP */ 455 | 456 | /* set Note State */ 457 | function internalSetConnectState (cState) { 458 | debug('internalSetConnectState:','new state:'+cState,'old state:'+node.system.connectState) 459 | if ((!node.system.connectState) || node.system.connectState != cState) { 460 | debug('internalSetConnectState:','set state:'+cState) 461 | node.system.connectState = cState 462 | node.system.connectStateText = adsHelpers.connectState.fromId(cState) 463 | if (node.system.connectState != adsHelpers.connectState.CONNECTED) { 464 | internalSetAdsState(nodeads.ADSSTATE.INVALID) 465 | } 466 | internalSystemUpdate() 467 | } 468 | } 469 | /* end Note State */ 470 | 471 | /* symbols from PLC */ 472 | node.getSymbols = function (force,cb) { 473 | debug('getSymbols:','enter') 474 | if (!force && (twincat.symbolsCache)) { 475 | debug('getSymbols by cache') 476 | cb(twincat.symbolsCache) 477 | } else { 478 | if (node.system.connectState == adsHelpers.connectState.CONNECTED) { 479 | if (twincat.adsClient) { 480 | twincat.adsClient.getSymbols(callbackSafeIfStillConnected(twincat.adsClient, function (err, symbols){ 481 | debug('getSymbols:',err,symbols) 482 | if (err) { 483 | n.error(util.format('Ads Symbols %s', err)) 484 | } else { 485 | twincat.symbolsCache = symbols 486 | cb(symbols) 487 | } 488 | }, true )) 489 | } 490 | } 491 | } 492 | } 493 | 494 | node.getDatatyps = function (force,cb) { 495 | debug('getDatatyps:','enter') 496 | if (!force && (twincat.datatypsCache)) { 497 | debug('getDatatyps by cache') 498 | cb(twincat.datatypsCache) 499 | } else { 500 | if (node.system.connectState == adsHelpers.connectState.CONNECTED) { 501 | if (twincat.adsClient) { 502 | twincat.adsClient.getDatatyps(callbackSafeIfStillConnected(twincat.adsClient, function (err, datatyps){ 503 | debug('getDatatyps:',err,datatyps) 504 | if (err) { 505 | n.error(util.format('Ads Datatyps %s', err)) 506 | } else { 507 | twincat.datatypsCache = datatyps 508 | cb(datatyps) 509 | } 510 | }, true )) 511 | } 512 | } 513 | } 514 | } 515 | /* end symbols from PLC */ 516 | 517 | /* system Nodes */ 518 | node.systemRegister = function (n){ 519 | debug('systemRegister:',n.id,n.type) 520 | if (node.systemNodes.indexOf(n) < 0) { 521 | node.systemNodes.push(n) 522 | node.systemUpdate(n) 523 | setSystemStatus(n) 524 | } 525 | } 526 | 527 | node.systemUnregister = function (n){ 528 | debug('systemUnregister:',n.id,n.type) 529 | var index = node.systemNodes.indexOf(n) 530 | if (index >= 0) { 531 | node.systemNodes.splice(index,1) 532 | } 533 | } 534 | 535 | function internalSystemUpdate () { 536 | debug('internalSystemUpdate:','enter') 537 | if (!(node.timerSU)) { 538 | node.timerSU = setInterval(function () { 539 | clearTimeout(node.timerSU) 540 | delete(node.timerSU) 541 | if (node.systemNodes) { 542 | node.systemNodes.forEach(function(n){ 543 | debug('internalSystemUpdate:','call',n.id,n.type) 544 | node.systemUpdate(n) 545 | setSystemStatus(n) 546 | }) 547 | } 548 | },50) 549 | } 550 | } 551 | 552 | node.systemUpdate = function (n) { 553 | if (n) { 554 | debug('systemUpdate:','enter',n.id,n.type) 555 | n.onData(node.system) 556 | } 557 | } 558 | 559 | function setSystemStatus (n) { 560 | debug('setSystemStatus:','enter') 561 | var fillSystem = "grey" 562 | var shapeSystem = "ring" 563 | var textSystem = node.system.connectStateText 564 | switch (node.system.connectState) { 565 | case adsHelpers.connectState.ERROR: 566 | fillSystem = "red" 567 | break 568 | case adsHelpers.connectState.DISCONNECTED: 569 | fillSystem = "grey" 570 | break 571 | case adsHelpers.connectState.CONNECTING: 572 | case adsHelpers.connectState.DISCONNECTING: 573 | fillSystem = "yellow" 574 | break 575 | case adsHelpers.connectState.CONNECTED: 576 | fillSystem = "green" 577 | shapeSystem = "dot" 578 | textSystem = node.system.adsStateText 579 | switch (node.system.adsState) { 580 | case nodeads.ADSSTATE.INVALID: 581 | fillSystem = "grey" 582 | break 583 | case nodeads.ADSSTATE.IDLE: 584 | case nodeads.ADSSTATE.RESET: 585 | case nodeads.ADSSTATE.INIT: 586 | case nodeads.ADSSTATE.POWERGOOD: 587 | case nodeads.ADSSTATE.SHUTDOWN: 588 | case nodeads.ADSSTATE.SUSPEND: 589 | case nodeads.ADSSTATE.RESUME: 590 | case nodeads.ADSSTATE.RECONFIG: 591 | fillSystem = "blue" 592 | break 593 | case nodeads.ADSSTATE.CONFIG: 594 | case nodeads.ADSSTATE.START: 595 | case nodeads.ADSSTATE.STOPPING: 596 | case nodeads.ADSSTATE.SAVECFG: 597 | case nodeads.ADSSTATE.LOADCFG: 598 | fillSystem = "yellow" 599 | break 600 | case nodeads.ADSSTATE.RUN: 601 | fillSystem = "green" 602 | break 603 | case nodeads.ADSSTATE.ERROR: 604 | case nodeads.ADSSTATE.STOP: 605 | case nodeads.ADSSTATE.POWERFAILURE: 606 | fillSystem = "red" 607 | break 608 | } 609 | break 610 | } 611 | debug('setSystemStatus:',n.id,n.type,{fill:fillSystem,shape:shapeSystem,text:textSystem}) 612 | n.status({fill:fillSystem,shape:shapeSystem,text:textSystem}) 613 | } 614 | 615 | function internalSetAdsState(adsState) { 616 | debugCyclic('internalSetAdsState:','new State:',adsState,'old State:',node.system.adsState) 617 | if ((!node.system.adsState) || node.system.adsState != adsState) { 618 | debug('internalSetAdsState:','set State:',adsState) 619 | node.system.adsState = adsState 620 | node.system.adsStateText = nodeads.ADSSTATE.fromId(adsState) 621 | internalSystemUpdate() 622 | } 623 | 624 | } 625 | /* end system Nodes*/ 626 | connect() 627 | } 628 | RED.nodes.registerType('ads-connection', adsConnectionNode) 629 | 630 | } 631 | -------------------------------------------------------------------------------- /ads-helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | exports.isTimezoneType = function (adsType) { 4 | return (adsType === "TIME" || 5 | adsType === "TIME_OF_DAY" || 6 | adsType === "TOD" || 7 | adsType === "DATE" || 8 | adsType === "DATE_AND_TIME" || 9 | adsType === "DT") 10 | } 11 | 12 | exports.isRawType = function (adsType) { 13 | return (adsType === "RAW") 14 | } 15 | 16 | exports.isStringType = function (adsType) { 17 | return (adsType === "STRING") 18 | } 19 | 20 | exports.isArrayType = function (adsType) { 21 | return (adsType === "ARRAY") 22 | } 23 | 24 | exports.checkPort = function (node,port,def) { 25 | if (port < 0x0000 || port > 0xFFFF) { 26 | port = def 27 | node.error("wrong port:",port) 28 | } 29 | } 30 | 31 | const connectState = { 32 | ERROR: -1, 33 | DISCONNECTED: 0, 34 | CONNECTING: 1, 35 | CONNECTED: 2, 36 | DISCONNECTING: 3, 37 | fromId: function(id) { 38 | var states = this 39 | var state 40 | Object.keys(states).map(function(key){if (states[key]==id) state=key}) 41 | return state 42 | } 43 | } 44 | exports.connectState = connectState 45 | 46 | exports.wildcardToRegExp = function (s) { 47 | return new RegExp('^' + s.replace(/[|\\{}()[\]^$+.]/g, '\\$&').replace(/[?]/g, ".").replace(/[*]/g, ".*") + '$') 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ads-in.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 108 | 109 | -------------------------------------------------------------------------------- /ads-in.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | 'use strict' 3 | var util = require('util') 4 | var debug = require('debug')('node-red-contrib-ads:adsInNode') 5 | 6 | function adsInNode(config) { 7 | RED.nodes.createNode(this, config) 8 | var node = this 9 | 10 | node.adsDatasource = RED.nodes.getNode(config.datasource) 11 | if (node.adsDatasource) { 12 | debug('config:',config) 13 | 14 | //node.onAdsData = function (handle){ 15 | // debug('onAdsData:','node.id',node.id,'node.symname',node.symname,'handle.value',handle.value) 16 | // var msg = {} 17 | // RED.util.setMessageProperty(msg, node.inValue, handle.value) 18 | // node.send(msg) 19 | // debug('onAdsData:','node.id',node.id,'node.symname',node.symname,'msg',msg) 20 | //} 21 | 22 | this.on("input", function(msg) { 23 | debug('input:',msg) 24 | var cfg = { 25 | useIndex: config.useIndex, 26 | symname: config.varName, 27 | indexGroup: config.indexGroup, 28 | indexOffset: config.indexOffset, 29 | adstype: config.varTyp, 30 | bytelength: config.varSize, 31 | array:config.isArray, 32 | lowindex:config.varLowIndex, 33 | highindex:config.varHighIndex, 34 | timezone: config.timezone, 35 | inValue: (config.inValue||'payload'), 36 | useInputMsg: (config.useInputMsg||false), 37 | //set topic by default to msg.topic 38 | topic: (msg.topic||'') 39 | } 40 | // overwrite default msg.topic by value in topic property (if used) 41 | if (String(config.topic).length > 0) { 42 | cfg.topic = config.topic 43 | } 44 | 45 | if (msg.config) { 46 | if (typeof msg.config.varName !== 'undefined') { 47 | cfg.symname = msg.config.varName 48 | } 49 | if (typeof msg.config.indexGroup !== 'undefined') { 50 | cfg.indexGroup = msg.config.indexGroup 51 | } 52 | if (typeof msg.config.indexOffset !== 'undefined') { 53 | cfg.indexOffset = msg.config.indexOffset 54 | } 55 | if (typeof msg.config.useIndex !== 'undefined') { 56 | cfg.useIndex = msg.config.useIndex 57 | } 58 | if (typeof msg.config.varType !== 'undefined') { 59 | cfg.adstype = msg.config.varType 60 | } 61 | if (typeof msg.config.varSize !== 'undefined') { 62 | cfg.bytelength = msg.config.varSize 63 | } 64 | if (typeof msg.config.isarray !== 'undefined') { 65 | cfg.array = msg.config.isarray 66 | } 67 | if (typeof msg.config.varLowIndex !== 'undefined') { 68 | cfg.lowindex = msg.config.varLowIndex 69 | } 70 | if (typeof msg.config.varHighIndex !== 'undefined') { 71 | cfg.highindex = msg.config.varHighIndex 72 | } 73 | if (typeof msg.config.timezone !== 'undefined') { 74 | cfg.timezone = msg.config.timezone 75 | } 76 | if (typeof msg.config.inProperty !== 'undefined') { 77 | cfg.inValue = msg.config.inProperty 78 | } 79 | if (typeof msg.config.useInputMsg !== 'undefined') { 80 | cfg.useInputMsg = msg.config.useInputMsg 81 | } 82 | // overwrite default msg.topic by value in msg.config.topic (if existing) 83 | if (typeof msg.config.topic !== 'undefined') { 84 | if (String(msg.config.topic).length > 0) { 85 | cfg.topic = msg.config.topic 86 | } 87 | } 88 | } 89 | 90 | if (cfg.useIndex) { 91 | delete(cfg.symname) 92 | cfg.indexGroup = parseInt(cfg.indexGroup.toString()) 93 | if (isNaN(cfg.indexGroup)) { 94 | cfg.indexGroup = 0 95 | } 96 | cfg.indexOffset = parseInt(cfg.indexOffset.toString()) 97 | if (isNaN(cfg.indexOffset)) { 98 | cfg.indexOffset = 0 99 | } 100 | } else { 101 | delete(cfg.indexGroup) 102 | delete(cfg.indexOffset) 103 | } 104 | 105 | cfg.hasTopic = String(cfg.topic).length > 0 106 | var outMsg = {} 107 | if (cfg.useInputMsg) { 108 | outMsg = Object.assign({},msg) 109 | } 110 | 111 | node.adsDatasource.read(node, cfg, function (handle){ 112 | RED.util.setMessageProperty(outMsg, cfg.inValue, handle.value) 113 | if (cfg.hasTopic) { 114 | outMsg.topic = cfg.topic 115 | } 116 | node.send(outMsg) 117 | debug('input:','node.id',node.id,'cfg.symname',cfg.symname,'outMsg',outMsg) 118 | }) 119 | }) 120 | 121 | node.on('close', function () { 122 | }) 123 | } 124 | } 125 | RED.nodes.registerType('ADS In', adsInNode) 126 | } 127 | -------------------------------------------------------------------------------- /ads-notification.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 104 | 105 | -------------------------------------------------------------------------------- /ads-notification.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | 'use strict' 3 | // var util = require('util') 4 | var debug = require('debug')('node-red-contrib-ads:adsNotificationNode') 5 | 6 | function adsNotificationNode(config) { 7 | RED.nodes.createNode(this, config) 8 | var node = this 9 | 10 | node.adsDatasource = RED.nodes.getNode(config.datasource) 11 | if (node.adsDatasource) { 12 | node.useIndex = config.useIndex, 13 | node.symname = config.varName, 14 | node.indexGroup = config.indexGroup, 15 | node.indexOffset = config.indexOffset, 16 | node.adstype = config.varTyp 17 | node.bytelength = config.varSize 18 | node.array = config.isArray 19 | node.lowindex = config.varLowIndex 20 | node.highindex = config.varHighIndex 21 | node.timezone = config.timezone 22 | node.transmissionMode = config.transmissionMode 23 | node.maxDelay = config.maxDelay 24 | node.cycleTime = config.cycleTime 25 | node.property = config.property||'payload' 26 | node.topic = config.topic||'' 27 | node.hasTopic = String(node.topic).length > 0 28 | if (node.useIndex) { 29 | delete(node.symname) 30 | node.indexGroup = parseInt(node.indexGroup.toString()) 31 | if (isNaN(node.indexGroup)) { 32 | node.indexGroup = 0 33 | } 34 | node.indexOffset = parseInt(node.indexOffset.toString()) 35 | if (isNaN(node.indexOffset)) { 36 | node.indexOffset = 0 37 | } 38 | } else { 39 | delete(node.indexGroup) 40 | delete(node.indexOffset) 41 | } 42 | 43 | debug('config:',node) 44 | 45 | node.onAdsData = function (handle){ 46 | debug('onAdsData:','node.id',node.id,'node.symname',node.symname,'handle.value',handle.value) 47 | var msg = {} 48 | RED.util.setMessageProperty(msg, node.property, handle.value) 49 | if (node.hasTopic) { 50 | msg.topic = node.topic 51 | } 52 | node.send(msg) 53 | debug('onAdsData:','node.id',node.id,'node.symname',node.symname,'msg',msg) 54 | // node.setStatus() 55 | } 56 | 57 | // node.setStatus = function (nodeState,txt){ 58 | 59 | // node.setAdsState = function (nodeState,txt){ 60 | // switch (nodeState) 61 | // case NOTCONNECTED: 62 | // break 63 | // case NOTCONNECTFEHLER: 64 | // break 65 | // case CONNECTEDOTHER: 66 | // break 67 | // case CONNECTEDRUN: 68 | // break 69 | //red, green, yellow, blue or grey 70 | 71 | // if (txt) { 72 | 73 | // node.status({fill:"red",shape:"ring",text:node.status.txt}) 74 | // } 75 | // } 76 | 77 | node.adsDatasource.subscribe(node) 78 | 79 | node.on('close', function (done) { 80 | debug('close:','enter') 81 | node.adsDatasource.unsubscribe(node,done) 82 | }) 83 | 84 | } 85 | } 86 | RED.nodes.registerType('ADS Notification', adsNotificationNode) 87 | } 88 | -------------------------------------------------------------------------------- /ads-out.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 87 | 88 | -------------------------------------------------------------------------------- /ads-out.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | 'use strict' 3 | var util = require('util') 4 | var debug = require('debug')('node-red-contrib-ads:adsOutNode') 5 | 6 | function adsOutNode(config) { 7 | RED.nodes.createNode(this, config) 8 | var node = this 9 | node.adsDatasource = RED.nodes.getNode(config.datasource) 10 | if (node.adsDatasource) { 11 | debug('config:',config) 12 | 13 | this.on("input", function(msg) { 14 | debug('adsNotificationNode:','onAdsData:','node.id',node.id,'msg',msg) 15 | var cfg = { 16 | useIndex: config.useIndex, 17 | symname: config.varName, 18 | indexGroup: config.indexGroup, 19 | indexOffset: config.indexOffset, 20 | adstype: config.varTyp, 21 | bytelength: config.varSize, 22 | timezone: config.timezone, 23 | outValue: config.outValue, 24 | topic: (config.topic||'') 25 | } 26 | 27 | if (typeof msg.config !== 'undefined') { 28 | if (typeof msg.config.varName !== 'undefined') { 29 | cfg.symname = msg.config.varName 30 | } 31 | if (typeof msg.config.indexGroup !== 'undefined') { 32 | cfg.indexGroup = msg.config.indexGroup 33 | } 34 | if (typeof msg.config.indexOffset !== 'undefined') { 35 | cfg.indexOffset = msg.config.indexOffset 36 | } 37 | if (typeof msg.config.useIndex !== 'undefined') { 38 | cfg.useIndex = msg.config.useIndex 39 | } 40 | if (typeof msg.config.varType !== 'undefined') { 41 | cfg.adstype = msg.config.varType 42 | } 43 | if (typeof msg.config.varSize !== 'undefined') { 44 | cfg.bytelength = msg.config.varSize 45 | } 46 | if (typeof msg.config.timezone !== 'undefined') { 47 | cfg.timezone = msg.config.timezone 48 | } 49 | if (typeof msg.config.outProperty !== 'undefined') { 50 | cfg.outValue = msg.config.outProperty 51 | } 52 | if (typeof msg.config.topic !== 'undefined') { 53 | cfg.topic = msg.config.topic||'' 54 | } 55 | } 56 | 57 | if (cfg.useIndex) { 58 | delete(cfg.symname) 59 | cfg.indexGroup = parseInt(cfg.indexGroup.toString()) 60 | if (isNaN(cfg.indexGroup)) { 61 | cfg.indexGroup = 0 62 | } 63 | cfg.indexOffset = parseInt(cfg.indexOffset.toString()) 64 | if (isNaN(cfg.indexOffset)) { 65 | cfg.indexOffset = 0 66 | } 67 | } else { 68 | delete(cfg.indexGroup) 69 | delete(cfg.indexOffset) 70 | } 71 | 72 | cfg.hasTopic = String(cfg.topic).length > 0 73 | debug('adsNotificationNode:','onAdsData:','node.id',node.id,'cfg',cfg) 74 | var value = RED.util.getMessageProperty(msg,(cfg.outValue||'payload')) 75 | if (value !== undefined && (!cfg.hasTopic || cfg.topic == msg.topic)) { 76 | node.adsDatasource.write(node,cfg,value) 77 | } 78 | }) 79 | 80 | node.on('close', function () { 81 | }) 82 | } 83 | } 84 | RED.nodes.registerType('ADS Output', adsOutNode) 85 | } 86 | -------------------------------------------------------------------------------- /ads-symbols.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 32 | 33 | -------------------------------------------------------------------------------- /ads-symbols.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | 'use strict' 3 | var util = require('util') 4 | var adsHelpers = require('./ads-helpers') 5 | var debug = require('debug')('node-red-contrib-ads:adsSymbolsNode') 6 | 7 | function adsSymbolsNode(config) { 8 | RED.nodes.createNode(this, config) 9 | var node = this 10 | 11 | node.adsDatasource = RED.nodes.getNode(config.datasource) 12 | if (node.adsDatasource) { 13 | node.adscfg = { 14 | data: config.data, 15 | force: config.force || false 16 | } 17 | debug('config:',node) 18 | 19 | 20 | node.onData = function (data,topic){ 21 | debug('onData:','node.id',node.id,'node.type',node.type,'data',data) 22 | const msg = { 23 | payload: data 24 | } 25 | if (String(topic).length > 0) { 26 | msg.topic = topic 27 | } 28 | node.send(msg) 29 | } 30 | 31 | this.on("input", function(msg) { 32 | debug('input:','node.id',node.id,'node.type',node.type,'data',msg) 33 | var call = node.adsDatasource.getDatatyps 34 | 35 | var topic = msg.topic 36 | // overwrite default msg.topic by value in msg.config.topic (if existing) 37 | if (typeof (msg.config) !== 'undefined' && typeof (msg.config.topic) !== 'undefined' ) { 38 | topic = msg.config.topic 39 | // overwrite default msg.topic by value in topic property (if used) 40 | } else if (String(config.topic).length > 0 ){ 41 | topic = config.topic 42 | // else -> keep original msg.topic 43 | } 44 | if (node.adscfg.data == 'SYMBOLES') { 45 | call = node.adsDatasource.getSymbols 46 | } 47 | 48 | var ask = [] 49 | if (typeof msg.payload === 'string') { 50 | ask.push(adsHelpers.wildcardToRegExp(msg.payload.toUpperCase())) 51 | } else if (Array.isArray(msg.payload)) { 52 | msg.payload.map(function(p){ 53 | if (typeof p === 'string') { 54 | ask.push(adsHelpers.wildcardToRegExp(p.toUpperCase())) 55 | } 56 | }) 57 | } 58 | call(node.adscfg.force, function (data) { 59 | 60 | 61 | var out = [] 62 | if (ask.length > 0){ 63 | ask.map(function(m){ 64 | data.map(function(d){ 65 | if (d.name.match(m)) { 66 | out.push(d) 67 | } 68 | }) 69 | }) 70 | } else { 71 | out = data 72 | } 73 | node.onData(out,topic) 74 | }) 75 | 76 | }) 77 | 78 | node.on('close', function () { 79 | debug('close:','node.id',node.id,'node.type',node.type) 80 | }) 81 | } 82 | } 83 | RED.nodes.registerType('ADS Symbols', adsSymbolsNode) 84 | } 85 | -------------------------------------------------------------------------------- /ads-system.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 20 | 21 | -------------------------------------------------------------------------------- /ads-system.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | 'use strict' 3 | var util = require('util') 4 | var debug = require('debug')('node-red-contrib-ads:adsSystemNode') 5 | 6 | function adsSystemNode(config) { 7 | RED.nodes.createNode(this, config) 8 | var node = this 9 | 10 | node.adsDatasource = RED.nodes.getNode(config.datasource) 11 | if (node.adsDatasource) { 12 | node.topic = config.topic||'' 13 | node.hasTopic = String(node.topic).length > 0 14 | debug('config:',node) 15 | 16 | 17 | node.onData = function (data){ 18 | const msg = { 19 | payload: data 20 | } 21 | if (node.hasTopic) { 22 | msg.topic = node.topic 23 | } 24 | node.send(msg) 25 | debug('onData:','node.id',node.id,'node.type',node.type,'msg',msg) 26 | } 27 | 28 | node.adsDatasource.systemRegister(node) 29 | 30 | this.on("input", function(msg) { 31 | debug('input:','node.id',node.id,'node.type',node.type,'msg',msg) 32 | node.adsDatasource.systemUpdate(node) 33 | }) 34 | 35 | node.on('close', function () { 36 | debug('close:','node.id',node.id,'node.type',node.type,'enter') 37 | node.adsDatasource.systemUnregister(node) 38 | }) 39 | } 40 | } 41 | RED.nodes.registerType('ADS System', adsSystemNode) 42 | } 43 | -------------------------------------------------------------------------------- /icons/ic_syst.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLCHome/node-red-contrib-ads/f1431ba41cb0c730434c216b37b060a91723bcdd/icons/ic_syst.gif -------------------------------------------------------------------------------- /icons/ic_syst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLCHome/node-red-contrib-ads/f1431ba41cb0c730434c216b37b060a91723bcdd/icons/ic_syst.png -------------------------------------------------------------------------------- /icons/tcat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PLCHome/node-red-contrib-ads/f1431ba41cb0c730434c216b37b060a91723bcdd/icons/tcat.png -------------------------------------------------------------------------------- /locales/en-US/ads-connection.json: -------------------------------------------------------------------------------- 1 | { 2 | "ads-connection": { 3 | "label": { 4 | "host": "Host", 5 | "clientIP": "Client", 6 | "amsNetIdTarget": "Target NetId", 7 | "amsNetIdSource": "Source NetId", 8 | "port": "Port", 9 | "amsPortSource": "Source Port", 10 | "amsPortTarget": "Target Port", 11 | "adsTimeout" : "Timeout ms" 12 | }, 13 | "placeholder": { 14 | "host": "for example: 192.168.2.5", 15 | "clientIP": "leave empty for automatic", 16 | "amsNetIdTarget": "for example: 192.168.2.5.1.1", 17 | "amsNetIdSource": "for example: 192.168.2.10.1.1", 18 | "port": "for example: 48898", 19 | "amsPortSource": "for example: 32905", 20 | "amsPortTarget": "for example: 801", 21 | "adsTimeout": "for example: 500 in ms" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /locales/en-US/ads-in.json: -------------------------------------------------------------------------------- 1 | { 2 | "ads-in": { 3 | "label": { 4 | "datasource": "Datasource", 5 | "useIndex": "use index", 6 | "varName": "variable name", 7 | "indexGroup": "index group", 8 | "indexOffset": "index offset", 9 | "varTyp": "variable type", 10 | "isArray": "array", 11 | "varSize": "size in byte", 12 | "varLowIndex": "low index", 13 | "varHighIndex": "high index", 14 | "timezone": "timezone", 15 | "inValue": "property", 16 | "useInputMsg": "add property to incoming msg", 17 | "topic": "topic" 18 | }, 19 | "placeholder": { 20 | "varName": ".var", 21 | "indexGroup": "15 or 0xF", 22 | "indexOffset": "15 or 0xF", 23 | "varSize": "bytes", 24 | "varLowIndex": "0", 25 | "varHighIndex": "n", 26 | "inValue": "payload", 27 | "topic": "enter a topic" 28 | }, 29 | "varTyp": { 30 | "BOOL": "BOOL", 31 | "BYTE": "BYTE", 32 | "WORD": "WORD", 33 | "DWORD": "DWORD", 34 | "SINT": "SINT", 35 | "USINT": "USINT", 36 | "INT": "INT", 37 | "UINT": "UINT", 38 | "DINT": "DINT", 39 | "UDINT": "UDINT", 40 | "LINT": "LINT", 41 | "ULINT": "ULINT", 42 | "REAL": "REAL", 43 | "LREAL": "LREAL", 44 | "TIME": "TIME", 45 | "TIME_OF_DAY": "TIME_OF_DAY", 46 | "TOD": "TOD", 47 | "DATE": "DATE", 48 | "DATE_AND_TIME": "DATE_AND_TIME", 49 | "DT": "DT", 50 | "STRING": "STRING", 51 | "RAW": "RAW" 52 | }, 53 | "timezone": { 54 | "UNCHANGED": "unchanged", 55 | "TO_LOCAL": "convert to local timezone" 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /locales/en-US/ads-notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "ads-notification": { 3 | "label": { 4 | "datasource": "Datasource", 5 | "useIndex": "use index", 6 | "varName": "variable name", 7 | "indexGroup": "index group", 8 | "indexOffset": "index offset", 9 | "varTyp": "variable type", 10 | "isArray": "array", 11 | "varSize": "size in byte", 12 | "varLowIndex": "low index", 13 | "varHighIndex": "high index", 14 | "timezone": "timezone", 15 | "transmissionMode": "transmission mode", 16 | "maxDelay": "max delay [ms]", 17 | "cycleTime": "cycle time [ms]", 18 | "property": "property", 19 | "topic": "topic" 20 | }, 21 | "placeholder": { 22 | "varName": ".var", 23 | "indexGroup": "15 or 0xF", 24 | "indexOffset": "15 or 0xF", 25 | "varSize": "bytes", 26 | "varLowIndex": "0", 27 | "varHighIndex": "n", 28 | "maxDelay": "Latest time (in ms) after which the event has finished", 29 | "cycleTime": "Time (in ms) after which the PLC server checks whether the variable has changed", 30 | "property": "payload", 31 | "topic": "enter a topic" 32 | }, 33 | "varTyp": { 34 | "BOOL": "BOOL", 35 | "BYTE": "BYTE", 36 | "WORD": "WORD", 37 | "DWORD": "DWORD", 38 | "SINT": "SINT", 39 | "USINT": "USINT", 40 | "INT": "INT", 41 | "UINT": "UINT", 42 | "DINT": "DINT", 43 | "UDINT": "UDINT", 44 | "LINT": "LINT", 45 | "ULINT": "ULINT", 46 | "REAL": "REAL", 47 | "LREAL": "LREAL", 48 | "TIME": "TIME", 49 | "TIME_OF_DAY": "TIME_OF_DAY", 50 | "TOD": "TOD", 51 | "DATE": "DATE", 52 | "DATE_AND_TIME": "DATE_AND_TIME", 53 | "DT": "DT", 54 | "STRING": "STRING", 55 | "RAW": "RAW" 56 | }, 57 | "transmissionMode": { 58 | "ONCHANGE": "ONCHANGE", 59 | "CYCLIC": "CYCLIC" 60 | }, 61 | "timezone": { 62 | "UNCHANGED": "unchanged", 63 | "TO_LOCAL": "convert to local timezone" 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /locales/en-US/ads-out.json: -------------------------------------------------------------------------------- 1 | { 2 | "ads-out": { 3 | "label": { 4 | "datasource": "Datasource", 5 | "useIndex": "use index", 6 | "varName": "variable name", 7 | "indexGroup": "index group", 8 | "indexOffset": "index offset", 9 | "varTyp": "variable type", 10 | "outValue": "property", 11 | "varSize": "size in byte", 12 | "timezone": "timezone", 13 | "topic": "topic" 14 | }, 15 | "placeholder": { 16 | "varName": ".var", 17 | "indexGroup": "15 or 0xF", 18 | "indexOffset": "15 or 0xF", 19 | "outValue": "payload", 20 | "varSize": "byte", 21 | "topic": "enter a topic" 22 | }, 23 | "varTyp": { 24 | "BOOL": "BOOL", 25 | "BYTE": "BYTE", 26 | "WORD": "WORD", 27 | "DWORD": "DWORD", 28 | "SINT": "SINT", 29 | "USINT": "USINT", 30 | "INT": "INT", 31 | "UINT": "UINT", 32 | "DINT": "DINT", 33 | "UDINT": "UDINT", 34 | "LINT": "LINT", 35 | "ULINT": "ULINT", 36 | "REAL": "REAL", 37 | "LREAL": "LREAL", 38 | "TIME": "TIME", 39 | "TIME_OF_DAY": "TIME_OF_DAY", 40 | "TOD": "TOD", 41 | "DATE": "DATE", 42 | "DATE_AND_TIME": "DATE_AND_TIME", 43 | "DT": "DT", 44 | "STRING": "STRING", 45 | "RAW": "RAW" 46 | }, 47 | "timezone": { 48 | "UNCHANGED": "unchanged", 49 | "TO_LOCAL": "convert to local timezone" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /locales/en-US/ads-symbols.json: -------------------------------------------------------------------------------- 1 | { 2 | "ads-symbols": { 3 | "label": { 4 | "datasource": "Datasource", 5 | "data": "read what", 6 | "force": "force read from PLC", 7 | "topic": "topic" 8 | }, 9 | "placeholder": { 10 | "topic": "enter a topic" 11 | }, 12 | "data": { 13 | "SYMBOLES": "Symboles", 14 | "TYPES": "Types" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /locales/en-US/ads-system.json: -------------------------------------------------------------------------------- 1 | { 2 | "ads-system": { 3 | "label": { 4 | "datasource": "Datasource", 5 | "topic": "topic" 6 | }, 7 | "placeholder": { 8 | "topic": "enter a topic" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-ads", 3 | "version": "1.1.31", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "node-red-contrib-ads", 9 | "version": "1.1.31", 10 | "license": "MIT", 11 | "dependencies": { 12 | "node-ads-api": "^1.4.17" 13 | } 14 | }, 15 | "node_modules/debug": { 16 | "version": "3.2.7", 17 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 18 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 19 | "dependencies": { 20 | "ms": "^2.1.1" 21 | } 22 | }, 23 | "node_modules/ms": { 24 | "version": "2.1.3", 25 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 26 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 27 | }, 28 | "node_modules/node-ads-api": { 29 | "version": "1.4.17", 30 | "resolved": "https://registry.npmjs.org/node-ads-api/-/node-ads-api-1.4.17.tgz", 31 | "integrity": "sha512-2HLenH3SIX8xHSQcd5W8TRCBaoD01H7YHg3BQK2Ed3+in9HEYs9eIi3Dr8Y3taXCMRdNjGMDEli50bDqHSNk8Q==", 32 | "dependencies": { 33 | "debug": "^3.1.0", 34 | "safe-buffer": "^5.1.1" 35 | }, 36 | "engines": { 37 | "node": ">=0.10" 38 | } 39 | }, 40 | "node_modules/safe-buffer": { 41 | "version": "5.2.1", 42 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 43 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 44 | "funding": [ 45 | { 46 | "type": "github", 47 | "url": "https://github.com/sponsors/feross" 48 | }, 49 | { 50 | "type": "patreon", 51 | "url": "https://www.patreon.com/feross" 52 | }, 53 | { 54 | "type": "consulting", 55 | "url": "https://feross.org/support" 56 | } 57 | ] 58 | } 59 | }, 60 | "dependencies": { 61 | "debug": { 62 | "version": "3.2.7", 63 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 64 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 65 | "requires": { 66 | "ms": "^2.1.1" 67 | } 68 | }, 69 | "ms": { 70 | "version": "2.1.3", 71 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 72 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 73 | }, 74 | "node-ads-api": { 75 | "version": "1.4.17", 76 | "resolved": "https://registry.npmjs.org/node-ads-api/-/node-ads-api-1.4.17.tgz", 77 | "integrity": "sha512-2HLenH3SIX8xHSQcd5W8TRCBaoD01H7YHg3BQK2Ed3+in9HEYs9eIi3Dr8Y3taXCMRdNjGMDEli50bDqHSNk8Q==", 78 | "requires": { 79 | "debug": "^3.1.0", 80 | "safe-buffer": "^5.1.1" 81 | } 82 | }, 83 | "safe-buffer": { 84 | "version": "5.2.1", 85 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 86 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-ads", 3 | "version": "1.1.31", 4 | "description": "Provides nodes to talk to Beckhoff TwinCAT PLC variables over ADS", 5 | "license": "MIT", 6 | "dependencies": { 7 | "node-ads-api": "^1.4.17" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/PLCHome/node-red-contrib-ads.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/PLCHome/node-red-contrib-ads/issues" 15 | }, 16 | "homepage": "https://github.com/PLCHome/node-red-contrib-ads#readme", 17 | "keywords": [ 18 | "node-red", 19 | "ads", 20 | "beckhoff", 21 | "plc", 22 | "sps", 23 | "home", 24 | "automation" 25 | ], 26 | "node-red": { 27 | "nodes": { 28 | "ads-notification": "ads-notification.js", 29 | "ads-in": "ads-in.js", 30 | "ads-out": "ads-out.js", 31 | "ads-system": "ads-system.js", 32 | "ads-symbols": "ads-symbols.js", 33 | "ads-connection": "ads-connection.js" 34 | } 35 | }, 36 | "author": { 37 | "name": "Chris Traeger" 38 | } 39 | } 40 | --------------------------------------------------------------------------------