├── .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 Index Group | \n Index Offset | \n Name | \n Type | \n Comment | \n
\n \n {{ obj.indexGroup }} | \n {{ obj.indexOffset }} | \n {{ obj.name }} | \n {{ obj.type }} | \n {{ obj.comment }} | \n
\n
\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 Version | \n Size | \n Data Type | \n Name | \n Type | \n Comment | \n Array Dim | \n Sub Types | \n
\n \n {{ obj.version }} | \n {{ obj.size }} | \n {{ obj.dataType }} | \n {{ obj.name }} | \n {{ obj.type }} | \n {{ obj.comment }} | \n \n \n \n Low Bound | \n Elements | \n \n \n {{ sobj.lBound }} | \n {{ sobj.elements }} | \n \n \n | \n \n \n \n Index | \n Version | \n Size | \n Data Type | \n Offset | \n Name | \n Type | \n Comment | \n \n \n {{ sobj.index }} | \n {{ sobj.version }} | \n {{ sobj.size }} | \n {{ sobj.dataType }} | \n {{ sobj.offs }} | \n {{ sobj.name }} | \n {{ sobj.type }} | \n {{ sobj.comment }} | \n \n \n | \n
\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 |
--------------------------------------------------------------------------------