@teamcymbeline/node-red-contrib-mcprotocol 0.0.0-stage → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ node-red-contrib-mcprotocol
2
+ ===========================
2
3
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
+ ## About
5
+
6
+ ### Overview
7
+ This is a Node-RED node module to directly interface with MITSUBISHI PLCs over Ethernet using MC Protocol.
8
+
9
+ ### Features
10
+ - Both TCP and UDP connections are possible
11
+ - frames 1E, 3E and 4E are possible
12
+ - works for PLC types
13
+ - A *(See Note 1)*
14
+ - QnA
15
+ - Q
16
+ - L
17
+ - R
18
+ - ASCII and BINARY mode *(See Note 2)*
19
+
20
+ ### Recommendation
21
+ If your PLC suports UDP + 4E, this is by far most reliable.
22
+
23
+ ### NOTES
24
+ 1. For A series PLC, only 1E frames are supported
25
+ 2. ASCII mode is currently not supported for frames 3E and 4E
26
+ ---
27
+ ## Install
28
+
29
+ ### Prerequisites
30
+
31
+ * node.js (runtime for Node-RED)
32
+ * Node-RED
33
+ * [optional] git (Used for repository cloning/downloads)
34
+
35
+ ### Easy install
36
+
37
+ Use the Manage Palette > Install option from the menu inside node-red
38
+
39
+
40
+ ### NPM install
41
+ ```sh
42
+ cd ~/.node-red
43
+ npm install node-red-contrib-mcprotocol
44
+ ```
45
+
46
+ ### GIT install Direct
47
+ ```sh
48
+ cd ~/.node-red
49
+ npm install Steve-Mcl/node-red-contrib-mcprotocol
50
+ ```
51
+
52
+ ### GIT Install
53
+ Make a directory for the base files on the disk (somewhere secure) and open the created folder and open PowerShell (SHIFT + right_click) or "Git Bash Here" with right mouse inside the folder. Now enter the following:
54
+ ```sh
55
+ cd c:/tempsourcefolder
56
+ git clone https://github.com/Steve-Mcl/node-red-contrib-mcprotocol.git
57
+
58
+ cd ~/.node-red
59
+ npm install c:/tempsourcefolder/node-red-contrib-mcprotocol
60
+ ```
61
+
62
+ ---
63
+
64
+ ### Credits
65
+ * [plcpeople](https://github.com/plcpeople/mcprotocol) for the original implementation of mcprotocol
@@ -0,0 +1,161 @@
1
+ /*
2
+ MIT License
3
+
4
+ Copyright (c) 2019, 2020 Steve-Mcl
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
23
+ */
24
+
25
+ var pool = {};
26
+
27
+ module.exports = {
28
+ get: function(port, host, opts) {
29
+ var mcprotocol = require("./mcprotocol/mcprotocol.js");
30
+ var id = `mcprotocol: {host:'${host || ""}', port: ${port || "''"}}`;
31
+ //var id = `mcprotocol: {host:'${(host || "")}', port: ${(port || "''")}, frame:'${frame}', plcType:'${plcType}}`;
32
+ var node = this;
33
+
34
+ if (!pool[id]) {
35
+ pool[id] = (function() {
36
+ var options = opts || {};
37
+ options.host = host;
38
+ options.port = port;
39
+ options.protocol = options.protocol || "TCP";
40
+ options.ascii = options.ascii || false;
41
+ options.frame = options.frame || "3E";
42
+ options.plcType = options.plcType || "Q";
43
+ options.autoConnect =
44
+ options.autoConnect == "undefined" ? true : options.autoConnect;
45
+ options.preventAutoReconnect = false;
46
+ options.logLevel = options.logLevel || "WARN";
47
+ node.debug(`[mcprotocol] adding new connection to pool ~ ${id}`);
48
+
49
+ var mcp = new mcprotocol();
50
+
51
+ mcp.setDebugLevel(options.logLevel);
52
+ mcp.initiateConnection(options);
53
+ var connecting = false;
54
+ var obj = {
55
+ getMCP: function mcp() {
56
+ return mcp;
57
+ },
58
+ getAutoConnect: function() {
59
+ return options.autoConnect == true;
60
+ },
61
+ setAutoConnect: function(b) {
62
+ options.autoConnect = b == true;
63
+ },
64
+ _instances: 0,
65
+ write: function(addr, data, callback) {
66
+ if (!mcp.isConnected()) {
67
+ //this.connect();
68
+ throw new Error("Not connected!");
69
+ }
70
+ var reply = mcp.writeItems(addr, data, callback);
71
+ return reply;
72
+ },
73
+ read: function(addr, callback) {
74
+ if (!mcp.isConnected()) {
75
+ //this.connect();
76
+ throw new Error("Not connected!");
77
+ }
78
+ var reply = mcp.readItems(addr, callback);
79
+ return reply;
80
+ },
81
+ closeConnection: function() {
82
+ mcp.connectionReset();
83
+ options.preventAutoReconnect = true;
84
+ },
85
+ on: function(a, b) {
86
+ mcp.on(a, b);
87
+ },
88
+ connect: function() {
89
+ options.preventAutoReconnect = false;
90
+ if (mcp && !mcp.isConnected() && !connecting) {
91
+ connecting = true;
92
+ mcp.initiateConnection(options);
93
+ }
94
+ },
95
+ disconnect: function() {
96
+ this._instances -= 1;
97
+ if (this._instances <= 0) {
98
+ node.debug(`[mcprotocol] closing connection ~ ${id}`);
99
+ mcp.dropConnection();
100
+ mcp = null;
101
+ node.debug(`[mcprotocol] deleting connection from pool ~ ${id}`);
102
+ delete pool[id];
103
+ }
104
+ },
105
+ reinitialize: function() {
106
+ //in case of physical connection loss, it does not get the connection back
107
+ //when connecting again. the mcp object needs to be renewed.
108
+ this.disconnect();
109
+ if (!mcp) {
110
+ mcp = new mcprotocol();
111
+ connecting = false;
112
+ }
113
+ this.connect();
114
+ this._instances += 1;
115
+ },
116
+ };
117
+
118
+ mcp.on("error", function(e) {
119
+ if (mcp) {
120
+ node.error(`[mcprotocol] error ~ ${id}: ${e}`);
121
+ connecting = false;
122
+ if (options.autoConnect) {
123
+ setTimeout(function() {
124
+ if (options.autoConnect && !options.preventAutoReconnect) {
125
+ node.error(
126
+ `[mcprotocol] autoConnect call from error handler ~ ${id}`
127
+ );
128
+ obj.connect();
129
+ }
130
+ }, 1000); //parametrise
131
+ }
132
+ }
133
+ });
134
+ mcp.on("open", function() {
135
+ if (mcp) {
136
+ node.debug(`[mcprotocol] connected ~ ${id}`);
137
+ connecting = false;
138
+ }
139
+ });
140
+ mcp.on("close", function(err) {
141
+ node.debug(`[mcprotocol] connection closed ~ ${id}`);
142
+ connecting = false;
143
+ if (options.autoConnect) {
144
+ setTimeout(function() {
145
+ if (options.autoConnect && !options.preventAutoReconnect) {
146
+ node.debug(
147
+ `[mcprotocol] autoConnect call from close handler ~ ${id}`
148
+ );
149
+ obj.connect();
150
+ }
151
+ }, 1000); //parametrise
152
+ }
153
+ });
154
+
155
+ return obj;
156
+ })();
157
+ }
158
+ pool[id]._instances += 1;
159
+ return pool[id];
160
+ }
161
+ };
@@ -0,0 +1 @@
1
+ [{"id":"4fabbfaa.503e6","type":"MC Read","z":"766a1068.58afb","name":"","topic":"","connection":"8a67d1bd.c84ff","address":"topic","addressType":"msg","outputFormat":0,"errorHandling":"throw","outputs":1,"x":540,"y":200,"wires":[["909f6d71.d3977"]]},{"id":"9d18a8b9.d51988","type":"udp in","z":"766a1068.58afb","name":"","iface":"","port":"5002","ipv":"udp4","multicast":"false","group":"","datatype":"buffer","x":180,"y":360,"wires":[["a906dcf7.c23e5","14abe69.0aacc19"]]},{"id":"a906dcf7.c23e5","type":"debug","z":"766a1068.58afb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":350,"y":360,"wires":[]},{"id":"609554a9.b5e02c","type":"inject","z":"766a1068.58afb","name":"","props":[{"p":"topic","vt":"str"},{"p":"count","v":"50","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"DINT0,50","x":200,"y":140,"wires":[["b920e789.6f8f98"]]},{"id":"1c92fdaa.e58f42","type":"inject","z":"766a1068.58afb","name":"connect","props":[{"p":"connect","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"connect","x":370,"y":60,"wires":[["894f189d.2280f8"]]},{"id":"29dda2ac.8a8dae","type":"inject","z":"766a1068.58afb","name":"disconnect","props":[{"p":"disconnect","v":"true","vt":"bool"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"disconnect","x":380,"y":100,"wires":[["894f189d.2280f8"]]},{"id":"14abe69.0aacc19","type":"switch","z":"766a1068.58afb","name":"3E / 4E","property":"payload[0]","propertyType":"msg","rules":[{"t":"eq","v":"0x50","vt":"num"},{"t":"eq","v":"0x54","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":200,"y":420,"wires":[["1762bb1b.795375"],["b31e7337.5fd9"]]},{"id":"b31e7337.5fd9","type":"buffer-parser","z":"766a1068.58afb","name":"","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16le","name":"header","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"sid","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"acess_route=>network","offset":6,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"acess_route=>pc_no","offset":7,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"acess_route=>destination_module_station_No","offset":8,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>command","offset":15,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>sub_command","offset":17,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>device_no","offset":19,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"request_data=>device_code","offset":22,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>device_count","offset":23,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"buffer","name":"buf","offset":0,"length":-1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":370,"y":440,"wires":[["a2f00c21.a640e","9f013c7f.8bfa7"]]},{"id":"a2f00c21.a640e","type":"debug","z":"766a1068.58afb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":390,"y":480,"wires":[]},{"id":"9f013c7f.8bfa7","type":"switch","z":"766a1068.58afb","name":"Read? Write","property":"payload.request_data.command","propertyType":"msg","rules":[{"t":"eq","v":"0x0401","vt":"num"},{"t":"eq","v":"0x1401","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":570,"y":400,"wires":[["dd7886d0.f142f8"],["74cfa55d.b1adfc"]]},{"id":"909f6d71.d3977","type":"debug","z":"766a1068.58afb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":200,"wires":[]},{"id":"dd7886d0.f142f8","type":"function","z":"766a1068.58afb","name":"Build read reply","func":"//debugger\nvar registers =\t{\n D: {symbol: 'D', type: 'WORD', notation: 'Decimal', binary: 0xA8, ascii: 'D*', description: 'Data register'},\n\t W: {symbol: 'W', type: 'WORD', notation: 'Hexadecimal', binary: 0xB4, ascii: 'W*', description: 'Link register'},\n\t Z: {symbol: 'Z', type: 'WORD', notation: 'Decimal', binary: 0xCC, ascii: 'Z*', description: 'Index register'},\n\t R: {symbol: 'R', type: 'WORD', notation: 'Decimal', binary: 0xAF, ascii: 'R*', description: 'File register'},\n }\nvar regLookup = {\n 0xA8: registers.D,\n 0xB4: registers.W,\n 0xCC: registers.Z,\n 0xAF: registers.R,\n}\nvar frame = msg.payload.header == 0x54 ? \"4E\" : (msg.payload.header == 0x50 ? \"3E\" : \"??\");\n\nvar reg = regLookup[msg.payload.request_data.device_code];\nif (!reg) { \n node.error(`device code ${msg.payload.request_data.device_code} not supported`, msg);\n //todo: return error to MC Read node\n return null;//temp\n}\n\n//build read reply\nvar buf = Buffer.alloc(8192);\nvar pos = 0;\n\nvar plcmem = flow.get(\"plcmem\");\nif(!plcmem) {\n plcmem = { \"D\": [], \"R\": [], \"W\": [] };\n flow.set(\"plcmem\", plcmem);\n}\nvar data = plcmem[reg.symbol] || [];\nvar dataStart = 11; //3E\nif (frame == '4E') {\n dataStart = 15;\n pos = buf.writeUInt16LE(0x00D4, pos);//0 expectedResponse\n pos = buf.writeUInt16LE(msg.payload.sid, pos);//2 sid\n pos = buf.writeUInt16LE(0, pos);//4 fixed 0\n} else {\n pos = buf.writeUInt16LE(0x00D0, pos);//0 expectedResponse\n}\npos = buf.writeUInt8(msg.payload.acess_route.network, pos);//6 accessRouteNetworkNo\npos = buf.writeUInt8(msg.payload.acess_route.pc_no, pos);//7 accessPCNo\npos = buf.writeUInt16LE(msg.payload.acess_route.destination_module_station_No, pos);//8 accessRouteModuleIONo\npos = buf.writeUInt8(0, pos);//10 accessRouteModuleStationNo\npos = buf.writeUInt16LE((msg.payload.request_data.device_count*2)+2, pos);//11 UInt16LE length\npos = buf.writeUInt16LE(0, pos);//12 UInt16LE endCode\npos = dataStart;// - data start\n// debugger\nfor (let index = 0; index < msg.payload.request_data.device_count; index++) {\n const element = data[msg.payload.request_data.device_no + index] || 0;\n pos = buf.writeUInt16LE(element, pos);\n}\nmsg.origPayload = msg.payload;\nmsg.payload = buf.slice(0, pos);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":780,"y":380,"wires":[["5b3f3ef1.041e8"]]},{"id":"5b3f3ef1.041e8","type":"udp out","z":"766a1068.58afb","name":"","addr":"","iface":"","port":"","ipv":"udp4","outport":"","base64":false,"multicast":"false","x":1030,"y":380,"wires":[]},{"id":"f8a9b16f.529d7","type":"function","z":"766a1068.58afb","name":"Build write reply","func":"//debugger\nvar registers =\t{\n D: {symbol: 'D', type: 'WORD', notation: 'Decimal', binary: 0xA8, ascii: 'D*', description: 'Data register'},\n\t W: {symbol: 'W', type: 'WORD', notation: 'Hexadecimal', binary: 0xB4, ascii: 'W*', description: 'Link register'},\n\t Z: {symbol: 'Z', type: 'WORD', notation: 'Decimal', binary: 0xCC, ascii: 'Z*', description: 'Index register'},\n\t R: {symbol: 'R', type: 'WORD', notation: 'Decimal', binary: 0xAF, ascii: 'R*', description: 'File register'},\n }\nvar regLookup = {\n 0xA8: registers.D,\n 0xB4: registers.W,\n 0xCC: registers.Z,\n 0xAF: registers.R,\n}\nvar frame = msg.payload.header == 0x54 ? \"4E\" : (msg.payload.header == 0x50 ? \"3E\" : \"??\");\n\nvar reg = regLookup[msg.payload.request_data.device_code];\nif (!reg) { \n node.error(`device code ${msg.payload.request_data.device_code} not supported`, msg);\n //todo: return error to MC Read node\n return null;//temp\n}\n\n//build read reply\nvar buf = Buffer.alloc(8192);\nvar pos = 0;\n\nvar plcmem = flow.get(\"plcmem\") || {};\nvar data = plcmem[reg.symbol] || [];\n// debugger\nvar writeData = msg.payload.data.values;\nvar writeIndex = msg.payload.request_data.device_no;\nfor (let index = 0; index < msg.payload.request_data.device_count; index++) {\n data[writeIndex++] = writeData[index];\n}\n\nvar dataStart = 11; //3E\nif (frame == '4E') {\n dataStart = 15;\n pos = buf.writeUInt16LE(0x00D4, pos);//0 expectedResponse\n pos = buf.writeUInt16LE(msg.payload.sid, pos);//2 sid seqNum\n pos = buf.writeUInt16LE(0, pos);//4 fixed 0 dummy\n} else {\n pos = buf.writeUInt16LE(0x00D0, pos);//0 expectedResponse\n}\npos = buf.writeUInt8(msg.payload.acess_route.network, pos);//6 accessRouteNetworkNo\npos = buf.writeUInt8(msg.payload.acess_route.pc_no, pos);//7 accessPCNo\npos = buf.writeUInt16LE(0, pos);//8 accessRouteModuleIONo\npos = buf.writeUInt8(0, pos);//10 accessRouteModuleStationNo\npos = buf.writeUInt16LE(2, pos);//11 UInt16LE length\npos = buf.writeUInt16LE(0, pos);//13 UInt16LE endCode\npos = buf.writeUInt16LE(0, pos);//15 UInt16LE endDetail\n\nmsg.origPayload = msg.payload;\nmsg.payload = buf.slice(0, pos);\n\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1000,"y":460,"wires":[["5b3f3ef1.041e8"]]},{"id":"161497d7.583468","type":"buffer-parser","z":"766a1068.58afb","name":"get write data (3E)","data":"payload.buf","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16le","name":"values","offset":21,"length":-1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload.data","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":790,"y":460,"wires":[["f8a9b16f.529d7"]]},{"id":"1762bb1b.795375","type":"buffer-parser","z":"766a1068.58afb","name":"","data":"payload","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16le","name":"header","offset":0,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"acess_route=>network","offset":2,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"acess_route=>pc_no","offset":3,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"acess_route=>destination_module_station_No","offset":4,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>command","offset":11,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>sub_command","offset":13,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>device_no","offset":15,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"byte","name":"request_data=>device_code","offset":18,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"request_data=>device_count","offset":23,"length":1,"offsetbit":0,"scale":"1","mask":""},{"type":"uint16le","name":"buf","offset":0,"length":-1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":370,"y":400,"wires":[["9f013c7f.8bfa7"]]},{"id":"74cfa55d.b1adfc","type":"switch","z":"766a1068.58afb","name":"3E / 4E","property":"payload.header","propertyType":"msg","rules":[{"t":"eq","v":"0x50","vt":"num"},{"t":"eq","v":"0x54","vt":"num"}],"checkall":"true","repair":false,"outputs":2,"x":580,"y":480,"wires":[["161497d7.583468"],["713e004a.8d583"]]},{"id":"713e004a.8d583","type":"buffer-parser","z":"766a1068.58afb","name":"get write data (4E)","data":"payload.buf","dataType":"msg","specification":"spec","specificationType":"ui","items":[{"type":"uint16le","name":"values","offset":25,"length":-1,"offsetbit":0,"scale":"1","mask":""}],"swap1":"","swap2":"","swap3":"","swap1Type":"swap","swap2Type":"swap","swap3Type":"swap","msgProperty":"payload.data","msgPropertyType":"str","resultType":"keyvalue","resultTypeType":"output","multipleResult":false,"fanOutMultipleResult":false,"setTopic":true,"outputs":1,"x":790,"y":500,"wires":[["f8a9b16f.529d7","fe2f26eb.73faa8"]]},{"id":"894f189d.2280f8","type":"MC Write","z":"766a1068.58afb","name":"","topic":"","connection":"8a67d1bd.c84ff","data":"payload","address":"topic","addressType":"msg","dataType":"msg","errorHandling":"throw","outputs":1,"x":540,"y":140,"wires":[["d1d847e5.f14728"]]},{"id":"d1d847e5.f14728","type":"debug","z":"766a1068.58afb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":140,"wires":[]},{"id":"39868be.7425874","type":"inject","z":"766a1068.58afb","name":"","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"D0,5","payload":"","payloadType":"str","x":190,"y":200,"wires":[["4fabbfaa.503e6"]]},{"id":"fe2f26eb.73faa8","type":"debug","z":"766a1068.58afb","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":970,"y":500,"wires":[]},{"id":"b920e789.6f8f98","type":"function","z":"766a1068.58afb","name":"Build write data","func":"var count = msg.count || 5\nvar data = [];\nfor (let index = 0; index < count; index++) {\n data[index] = index;\n}\n\nmsg.payload = data;\n\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":360,"y":140,"wires":[["894f189d.2280f8"]]},{"id":"d5772eff.5f0b7","type":"comment","z":"766a1068.58afb","name":"Basic Mitsubishi Q Emulator (Supports Z, D, R, W areas only)","info":"","x":340,"y":320,"wires":[]},{"id":"8a67d1bd.c84ff","type":"MC Protocol Connection","name":"","host":"localhost","port":"5002","protocol":"UDP","frame":"4E","plcType":"Q","ascii":false,"PLCStation":"","PCStation":"","PLCModuleNo":"","network":"","octalInputOutput":false,"timeout":"1234"}]
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Dana Moffit
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
+
@@ -0,0 +1,192 @@
1
+ mcprotocol
2
+ ==========
3
+
4
+ mcprotocol is a library that allows communication to Mitsubishi PLCs (currently only FX3U tested) using the MC (MELSEC Communication) Ethernet protocol as documented by Mitsubishi.
5
+
6
+ This software is not affiliated with Mitsubishi in any way, nor am I. FX3U and MELSEC are trademarks of Mitsubishi.
7
+
8
+ WARNING - This code is VERY MUCH BETA. Many things are untested (e.g. every combination of PLC / frame / ascii mode / device code / device number / count have not yet been carried out). You need to be aware that WRONG VALUES could be written to WRONG LOCATIONS. Fully test everything you do. In situations where writing to a PLC could cost you money, back up your data and test this fully. If there is any possibily of injury to someone or something, consider other software.
9
+
10
+ It is optimized - it sorts a large number of items being requested from the PLC and decides what overall data areas to request, then it groups multiple small requests together in a single packet or number of packets up to the maximum length the protocol supports. So a request for 100 different bits, all close (but not necessarily completely contiguous) will be grouped in one single request to the PLC, with no additional direction from the user.
11
+
12
+ mcprotocol manages reconnects for you. So if the connection is lost because the PLC is powered down or disconnected, you can continue to request data with no other action necessary. "Bad" values are returned, and eventually the connection will be automatically restored.
13
+
14
+ mcprotocol is written entirely in JavaScript, so no compiler or Python installation is necessary on Windows, and deployment on other platforms (ARM, etc) should be trivial.
15
+
16
+ Either ASCII or binary communication is supported on 1E frame, only binary mode is currently supported on 3E and 4E frames. Binary communication is the default, as it is faster as less data is actually sent.
17
+
18
+ This has been tested only on direct connection to FX3U-ENET, FX3U-ENET-ADP and Q-Series E71. Any Mitsubishi PLC or PLC Ethernet Card that supports frame 1E, 3E or 4E should (in theory) work. Serial port access is not supported - the protocol is slightly different. This only sends and receives "A, QnA, Q/L compatible 1E, 3E, 4E frames".
19
+
20
+ UDP connections are not currently possible.
21
+
22
+ __To configure a compatible connection on your PLC...__<br>
23
+ Under PLC Parameter, Ethernet Setting, Open Setting, make sure one or more of the connections are set up as "TCP", "MC Protocol".
24
+
25
+ _If "MC Protocol" is not available..._
26
+ * Protocol "TCP"
27
+ * Open System "Unpassive" (Fixed buffer can be send or receive if using a FX3U-ENET)
28
+ * Fixed Buffer Communication Procedure set to "Procedure Exist (MC)"
29
+ * "Pairing Open" set to "Disable"
30
+ * Existence Confirmation set to "Confirm" (No Confirm works as well, but can keep connections open for a long time causing failed reconnects)
31
+ * "Port" (1025 ~ 1029 are values I choose on Q Series. YMMV)
32
+
33
+ __To get started:__
34
+
35
+ npm install mcprotocol
36
+
37
+ Example usage:
38
+
39
+ var mc = require('mcprotocol');
40
+ var conn = new mc;
41
+ var doneReading = false;
42
+ var doneWriting = false;
43
+
44
+ var variables = { TEST1: 'D0,5', // 5 words starting at D0
45
+ TEST2: 'M6990,28', // 28 bits at M6990
46
+ TEST3: 'CN199,2', // ILLEGAL as CN199 is 16-bit, CN200 is 32-bit, must request separately
47
+ TEST4: 'R2000,2', // 2 words at R2000
48
+ TEST5: 'X034', // Simple input
49
+ TEST6: 'D6000.1,20', // 20 bits starting at D6000.1
50
+ TEST7: 'D6001.2', // Single bit at D6001
51
+ TEST8: 'S4,2', // 2 bits at S4
52
+ TEST9: 'RFLOAT5000,40' // 40 floating point numbers at R5000
53
+ }; // See setTranslationCB below for more examples
54
+
55
+ conn.initiateConnection({port: 1281, host: '192.168.0.2', ascii: false}, connected);
56
+
57
+ function connected(err) {
58
+ if (typeof(err) !== "undefined") {
59
+ // We have an error. Maybe the PLC is not reachable.
60
+ console.log(err);
61
+ process.exit();
62
+ }
63
+ conn.setTranslationCB(function(tag) {return variables[tag];}); // This sets the "translation" to allow us to work with object names defined in our app not in the module
64
+ conn.addItems(['TEST1', 'TEST4']);
65
+ conn.addItems('TEST6');
66
+ // conn.removeItems(['TEST2', 'TEST3']); // We could do this.
67
+ // conn.writeItems(['TEST5', 'TEST7'], [ true, true ], valuesWritten); // You can write an array of items as well.
68
+ conn.writeItems('TEST4', [ 666, 777 ], valuesWritten); // You can write a single array item too.
69
+ conn.readAllItems(valuesReady);
70
+ }
71
+
72
+ function valuesReady(anythingBad, values) {
73
+ if (anythingBad) { console.log("SOMETHING WENT WRONG READING VALUES!!!!"); }
74
+ console.log(values);
75
+ doneReading = true;
76
+ if (doneWriting) { process.exit(); }
77
+ }
78
+
79
+ function valuesWritten(anythingBad) {
80
+ if (anythingBad) { console.log("SOMETHING WENT WRONG WRITING VALUES!!!!"); }
81
+ console.log("Done writing.");
82
+ doneWriting = true;
83
+ if (doneReading) { process.exit(); }
84
+ }
85
+
86
+
87
+ This produces the following output, excluding some logs from mcprotocol.js itself:
88
+
89
+ Done writing.
90
+ { TEST1: [ 0, 0, 0, 0, 0 ],
91
+ TEST6:
92
+ [ true,
93
+ false,
94
+ true,
95
+ false,
96
+ false,
97
+ true,
98
+ false,
99
+ false,
100
+ false,
101
+ false,
102
+ false,
103
+ false,
104
+ false,
105
+ true,
106
+ false,
107
+ true,
108
+ true,
109
+ false,
110
+ true,
111
+ true ],
112
+ TEST4: [ 666, 777 ] }
113
+
114
+
115
+
116
+ ### API
117
+ - [initiateConnection()](#initiate-connection)
118
+ - [dropConnection()](#drop-connection)
119
+ - [setTranslationCB()](#set-translation-cb)
120
+ - [addItems()](#add-items)
121
+ - [removeItems()](#remove-items)
122
+ - [writeItems()](#write-items)
123
+ - [readAllItems()](#read-all-items)
124
+
125
+
126
+ #### <a name="initiate-connection"></a>mcprotocol.initiateConnection(params, callback)
127
+ Connects to a PLC.
128
+
129
+ params should be an object with the following keys:
130
+ - port (must match setting in configurator - note this is for TCP connections only, see other setup suggestions above)
131
+ - host (ip address or host name)
132
+ - ascii (default false, set to true if you have set this setting this way in the configurator for the Ethernet module)
133
+ - octalInputOutput (default true, set to false if you really don't like the fact that the program converts Xxxx and Yxxx addresses from Octal to Decimal, ensure you connect before you add items if you're going to set this to false) If left at the default, addresses line up with GXWorks/GXDeveloper.
134
+
135
+ `callback(err)` will be executed on success or failure. err is either an error object, or undefined on successful connection.
136
+
137
+ #### <a name="drop-connection"></a>mcprotocol.dropConnection()
138
+ Disconnects from a PLC.
139
+
140
+ This simply terminates the TCP connection.
141
+
142
+
143
+ #### <a name="set-translation-cb"></a>mcprotocol.setTranslationCB(translator)
144
+ Sets a callback for name - address translation.
145
+
146
+ This is optional - you can choose to use "addItem" etc with absolute addresses.
147
+
148
+ If you use it, `translator` should be a function that takes a string as an argument, and returns a string in the following format:
149
+ - <memory area>[type modifier]<device offset><.bit offset><,array length>
150
+ - Examples:
151
+ - M100,20 - 20 boolean values starting at M100, ending at M119
152
+ - X0,8 - First 8 inputs
153
+ - TS190 - Status of timer 290 (boolean)
154
+ - TN190 - Elapsed time value of timer 290 (integer)
155
+ - CS190 - Status of counter 190
156
+ - CN220 - Count value (double integer as it's over 200)
157
+ - DFLOAT1000 - Floating point value starting at D1000 (and including D1001 as float takes 2 words)
158
+ - D2000,5 - 5 values starting at D2000
159
+ - RDINT80 - R80 and R81 as a DINT
160
+ - RSTR30,10 - String that is 10 characters (5 words) starting at R30
161
+ - S5 - Boolean at S5
162
+ - RFLOAT20,5 - Floating point values starting at R20-21 and ending R28-29
163
+ - Y010 - Output at Y010 (actually the 9th output as outputs are in order Y000-Y008 then Y010)
164
+ - D1000.2 - Bit 2 of word D1000
165
+ - D1000.2,5 - 5 bits starting at D1000.2
166
+
167
+ In the example above, an object is declared and the `translator` references that object. It could just as reference a file or database. In any case, it allows cleaner Javascript code to be written that refers to a name instead of an absolute address.
168
+
169
+
170
+ #### <a name="add-items"></a>mcprotocol.addItems(items)
171
+ Adds `items` to the internal read polling list.
172
+
173
+ `items` can be a string or an array of strings.
174
+
175
+ #### <a name="remove-items"></a>mcprotocol.removeItems(items)
176
+ Removes `items` to the internal read polling list.
177
+
178
+ `items` can be a string or an array of strings.
179
+
180
+ #### <a name="write-items"></a>mcprotocol.writeItems(items, values)
181
+ Writes `items` to the PLC using the corresponding `values`.
182
+
183
+ `items` can be a string or an array of strings. If `items` is a single string, `values` should then be a single item (or an array if `items` is an array item). If `items` is an array of strings, `values` must be an array.
184
+
185
+
186
+ #### <a name="read-all-items"></a>mcprotocol.readAllItems(callback)
187
+ Reads the internal polling list and calls `callback` when done.
188
+
189
+ `callback(err, values)` is called with two arguments - a boolean indicating if ANY of the items have "bad quality", and `values`, an object containing the values being read as keys and their value (from the PLC) as the value.
190
+
191
+
192
+