ts-communication 1.0.0

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.
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SiemensS7Net = exports.SiemensPLCS = void 0;
4
+ exports.parseS7Address = parseS7Address;
5
+ const Core_1 = require("../core/Core");
6
+ var SiemensPLCS;
7
+ (function (SiemensPLCS) {
8
+ SiemensPLCS[SiemensPLCS["S1200"] = 0] = "S1200";
9
+ SiemensPLCS[SiemensPLCS["S300"] = 1] = "S300";
10
+ SiemensPLCS[SiemensPLCS["S400"] = 2] = "S400";
11
+ SiemensPLCS[SiemensPLCS["S1500"] = 3] = "S1500";
12
+ SiemensPLCS[SiemensPLCS["S200Smart"] = 4] = "S200Smart";
13
+ SiemensPLCS[SiemensPLCS["S200"] = 5] = "S200";
14
+ })(SiemensPLCS || (exports.SiemensPLCS = SiemensPLCS = {}));
15
+ function parseS7Address(input) {
16
+ try {
17
+ let s = input.trim().toUpperCase(), db = 0;
18
+ const dm = s.match(/^DB(\d+)\.(?:DB[BDW]?|)(\d+)(?:\.(\d+))?$/);
19
+ if (dm) {
20
+ db = Number(dm[1]);
21
+ return Core_1.OperateResult.CreateSuccessResult({
22
+ area: 0x84,
23
+ db,
24
+ byteAddress: Number(dm[2]),
25
+ bit: Number(dm[3] ?? 0)
26
+ });
27
+ }
28
+ const m = s.match(/^(PIW|PQW|SM|AI|AQ|I|Q|M|V|P|T|C)(\d+)(?:\.(\d+))?$/);
29
+ if (!m)
30
+ throw new Error(`Invalid Siemens address: ${input}`);
31
+ const area = {
32
+ I: 0x81,
33
+ Q: 0x82,
34
+ M: 0x83,
35
+ V: 0x84,
36
+ SM: 0x05,
37
+ P: 0x80, PIW: 0x80, PQW: 0x80, AI: 0x06, AQ: 0x07,
38
+ T: 0x1f,
39
+ C: 0x1e
40
+ };
41
+ return Core_1.OperateResult.CreateSuccessResult({
42
+ area: area[m[1]],
43
+ db: 0,
44
+ byteAddress: Number(m[2]),
45
+ bit: Number(m[3] ?? 0)
46
+ });
47
+ }
48
+ catch (e) {
49
+ return new Core_1.OperateResult(e.message);
50
+ }
51
+ }
52
+ class SiemensS7Net extends Core_1.DeviceClient {
53
+ Port = 102;
54
+ Rack = 0;
55
+ Slot = 0;
56
+ ConnectionType = 2;
57
+ LocalTSAP = 0x0100;
58
+ DestTSAP = 0x0102;
59
+ PDULength = 480;
60
+ PlcType;
61
+ initialized = false;
62
+ initializing;
63
+ constructor(siemens = SiemensPLCS.S1200, ipAddress, port) {
64
+ super(ipAddress, ipAddress === undefined ? undefined : 102);
65
+ this.Port = port ?? 102;
66
+ this.PlcType = siemens;
67
+ // S7 values are transmitted most-significant byte first.
68
+ this.ByteTransform = new Core_1.ByteTransform(Core_1.DataFormat.ABCD);
69
+ }
70
+ static parseTpkt(data) {
71
+ if (data.length < 4 || data[0] !== 3)
72
+ throw new Error('invalid Siemens TPKT response');
73
+ const length = data.readUInt16BE(2);
74
+ if (length < 4 || data.length < length)
75
+ throw new Error('incomplete Siemens TPKT response');
76
+ return data.subarray(4, length);
77
+ }
78
+ item(area, length, transportSize = 2) {
79
+ const item = Buffer.alloc(12);
80
+ item[0] = 0x12;
81
+ item[1] = 0x0a;
82
+ item[2] = 0x10;
83
+ item[3] = transportSize;
84
+ item.writeUInt16BE(length, 4);
85
+ item.writeUInt16BE(area.db, 6);
86
+ item[8] = area.area;
87
+ const address = area.byteAddress * 8 + area.bit;
88
+ item[9] = (address >>> 16) & 0xff;
89
+ item[10] = (address >>> 8) & 0xff;
90
+ item[11] = address & 0xff;
91
+ return item;
92
+ }
93
+ packet(area, length, write) {
94
+ const isWrite = write !== undefined;
95
+ const parameter = this.item(area, isWrite ? write.length : length, isWrite ? 2 : 2);
96
+ const data = isWrite ? Buffer.concat([Buffer.from([0, 4, (write.length * 8) >>> 8, (write.length * 8) & 0xff]), write, write.length % 2 ? Buffer.from([0]) : Buffer.alloc(0)]) : Buffer.alloc(0);
97
+ const s7 = Buffer.alloc(12 + parameter.length + data.length);
98
+ s7[0] = 0x32;
99
+ s7[1] = 1;
100
+ s7[2] = 0;
101
+ s7[3] = 0;
102
+ s7.writeUInt16BE(this.nextMessageId(), 4);
103
+ s7.writeUInt16BE(parameter.length, 6);
104
+ s7.writeUInt16BE(data.length, 8);
105
+ s7[10] = isWrite ? 5 : 4;
106
+ s7[11] = 1;
107
+ parameter.copy(s7, 12);
108
+ data.copy(s7, 12 + parameter.length);
109
+ const tpkt = Buffer.concat([Buffer.from([3, 0, 0, 0, 2, 0xf0, 0x80]), s7]);
110
+ tpkt.writeUInt16BE(tpkt.length, 2);
111
+ return tpkt;
112
+ }
113
+ messageId = 1;
114
+ nextMessageId() { const id = this.messageId; this.messageId = (this.messageId + 1) & 0xffff; return id; }
115
+ async ConnectServer() {
116
+ const connected = await super.ConnectServer();
117
+ if (!connected.IsSuccess || this.initialized)
118
+ return connected;
119
+ if (this.initializing)
120
+ return this.initializing;
121
+ this.initializing = (async () => {
122
+ const first = await this.requestConnected(this.connectionSetupPacket(), SiemensS7Net.parseTpkt);
123
+ if (!first.IsSuccess)
124
+ return first;
125
+ const second = await this.requestConnected(this.setupCommunicationPacket(), SiemensS7Net.parseTpkt);
126
+ if (!second.IsSuccess)
127
+ return second;
128
+ this.initialized = true;
129
+ return Core_1.OperateResult.CreateSuccessResult();
130
+ })().finally(() => { this.initializing = undefined; });
131
+ return this.initializing;
132
+ }
133
+ async ConnectClose() { this.initialized = false; this.initializing = undefined; return super.ConnectClose(); }
134
+ connectionSetupPacket() {
135
+ const packet = Buffer.from([3, 0, 0, 22, 0x11, 0xe0, 0, 0, 0, 1, 0, 0xc0, 1, 10, 0xc1, 2, 1, 2, 0xc2, 2, 1, 0]);
136
+ packet[20] = this.ConnectionType;
137
+ packet[21] = this.Rack * 32 + this.Slot;
138
+ return packet;
139
+ }
140
+ setupCommunicationPacket() {
141
+ const packet = Buffer.from([3, 0, 0, 25, 2, 0xf0, 0x80, 0x32, 1, 0, 0, 4, 0, 0, 8, 0, 0, 0xf0, 0, 0, 1, 0, 1, 1, 0xe0]);
142
+ return packet;
143
+ }
144
+ async transact(item) {
145
+ const r = await this.request(item, data => {
146
+ return SiemensS7Net.parseTpkt(data);
147
+ });
148
+ if (!r.IsSuccess)
149
+ return r;
150
+ const b = r.Content;
151
+ const marker = b.indexOf(0xff);
152
+ if (marker >= 0 && marker + 4 < b.length) {
153
+ const n = b.readUInt16BE(marker + 2);
154
+ return Core_1.OperateResult.CreateSuccessResult(b.subarray(marker + 4, marker + 4 + Math.ceil(n / 8)));
155
+ }
156
+ return Core_1.OperateResult.CreateSuccessResult(b);
157
+ }
158
+ async Read(address, length) {
159
+ const a = parseS7Address(address);
160
+ if (!a.IsSuccess)
161
+ return Core_1.OperateResult.CreateFailedResult(a);
162
+ return this.transact(this.packet(a.Content, length));
163
+ }
164
+ async ReadBool(address, length) {
165
+ const n = length ?? 1, r = await this.Read(address, Math.ceil(n / 8));
166
+ if (!r.IsSuccess)
167
+ return Core_1.OperateResult.CreateFailedResult(r);
168
+ const vals = Array.from({ length: n }, (_, i) => (r.Content[Math.floor(i / 8)] & (1 << (i % 8))) !== 0);
169
+ return Core_1.OperateResult.CreateSuccessResult(length === undefined ? vals[0] : vals);
170
+ }
171
+ async typedRead(address, length, width, fn) {
172
+ const r = await this.Read(address, length * width);
173
+ if (!r.IsSuccess)
174
+ return Core_1.OperateResult.CreateFailedResult(r);
175
+ const vals = Array.from({ length }, (_, i) => fn(r.Content, i * width));
176
+ return Core_1.OperateResult.CreateSuccessResult(length === 1 ? vals[0] : vals);
177
+ }
178
+ async ReadInt16(a, l) {
179
+ return this.typedRead(a, l ?? 1, 2, (b, o) => this.ByteTransform.TransInt16(b, o));
180
+ }
181
+ async ReadUInt16(a, l) {
182
+ return this.typedRead(a, l ?? 1, 2, (b, o) => this.ByteTransform.TransUInt16(b, o));
183
+ }
184
+ async ReadInt32(a, l) {
185
+ return this.typedRead(a, l ?? 1, 4, (b, o) => this.ByteTransform.TransInt32(b, o));
186
+ }
187
+ async ReadUInt32(a, l) {
188
+ return this.typedRead(a, l ?? 1, 4, (b, o) => this.ByteTransform.TransUInt32(b, o));
189
+ }
190
+ async ReadFloat(a, l) {
191
+ return this.typedRead(a, l ?? 1, 4, (b, o) => this.ByteTransform.TransSingle(b, o));
192
+ }
193
+ async ReadDouble(a, l) {
194
+ return this.typedRead(a, l ?? 1, 8, (b, o) => this.ByteTransform.TransDouble(b, o));
195
+ }
196
+ async ReadInt64(a, l) {
197
+ return this.typedRead(a, l ?? 1, 8, (b, o) => this.ByteTransform.TransInt64(b, o));
198
+ }
199
+ async ReadUInt64(a, l) {
200
+ return this.typedRead(a, l ?? 1, 8, (b, o) => this.ByteTransform.TransUInt64(b, o));
201
+ }
202
+ async ReadString(address, length, encoding = 'utf8') {
203
+ const r = await this.Read(address, length ?? 254);
204
+ return r.IsSuccess ? Core_1.OperateResult.CreateSuccessResult(this.ByteTransform.TransString(r.Content, encoding)) : Core_1.OperateResult.CreateFailedResult(r);
205
+ }
206
+ async Write(address, value) {
207
+ const a = parseS7Address(address);
208
+ if (!a.IsSuccess)
209
+ return a;
210
+ let data;
211
+ if (typeof value === 'boolean')
212
+ data = Buffer.from([value ? 1 : 0]);
213
+ else if (typeof value === 'string')
214
+ data = Buffer.from(value);
215
+ else if (Buffer.isBuffer(value))
216
+ data = value;
217
+ else if (Array.isArray(value) && typeof value[0] === 'boolean')
218
+ data = Buffer.from(value.map(v => v ? 1 : 0));
219
+ else
220
+ data = this.ByteTransform.TransByte(value, 2);
221
+ const r = await this.transact(this.packet(a.Content, data.length, data));
222
+ return r.IsSuccess ? Core_1.OperateResult.CreateSuccessResult() : r;
223
+ }
224
+ WriteByte(a, v) {
225
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.Byte(v)));
226
+ }
227
+ WriteInt16(a, v) {
228
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.Int16(v)));
229
+ }
230
+ WriteUInt16(a, v) {
231
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.UInt16(v)));
232
+ }
233
+ WriteInt32(a, v) {
234
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.Int32(v)));
235
+ }
236
+ WriteUInt32(a, v) {
237
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.UInt32(v)));
238
+ }
239
+ WriteFloat(a, v) {
240
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.Float(v)));
241
+ }
242
+ WriteDouble(a, v) {
243
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.Double(v)));
244
+ }
245
+ WriteInt64(a, v) {
246
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.Int64(v)));
247
+ }
248
+ WriteUInt64(a, v) {
249
+ return this.Write(a, this.ByteTransform.TransByte(new Core_1.UInt64(v)));
250
+ }
251
+ async ReadOrderNumber() {
252
+ return new Core_1.OperateResult('ReadOrderNumber requires an active S7 connection and negotiated PDU');
253
+ }
254
+ async HotStart() {
255
+ return new Core_1.OperateResult('HotStart is not supported by this minimal S7 transport');
256
+ }
257
+ async ColdStart() {
258
+ return new Core_1.OperateResult('ColdStart is not supported by this minimal S7 transport');
259
+ }
260
+ async Stop() {
261
+ return new Core_1.OperateResult('Stop is not supported by this minimal S7 transport');
262
+ }
263
+ async ForceBool() {
264
+ return new Core_1.OperateResult('ForceBool is not supported by this minimal S7 transport');
265
+ }
266
+ async CancelAllForce() {
267
+ return new Core_1.OperateResult('CancelAllForce is not supported by this minimal S7 transport');
268
+ }
269
+ async ConnectServerAsync() {
270
+ return this.ConnectServer();
271
+ }
272
+ async ConnectCloseAsync() {
273
+ return this.ConnectClose();
274
+ }
275
+ }
276
+ exports.SiemensS7Net = SiemensS7Net;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "ts-communication",
3
+ "version": "1.0.0",
4
+ "description": "工业 PLC 通讯库,支持 Siemens S7、Melsec MC、Omron FINS、Inovance 和 Modbus。",
5
+ "main": "dist/XCommunication.js",
6
+ "types": "dist/XCommunication.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "test": "npm run build && node test/protocol.test.js"
13
+ },
14
+ "keywords": [
15
+ "plc",
16
+ "industrial-automation",
17
+ "siemens",
18
+ "s7",
19
+ "melsec",
20
+ "omron",
21
+ "fins",
22
+ "inovance",
23
+ "modbus",
24
+ "modbus-tcp",
25
+ "modbus-rtu",
26
+ "typescript",
27
+ "nodejs"
28
+ ],
29
+ "author": "X Communication",
30
+ "license": "ISC",
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "type": "commonjs",
35
+ "devDependencies": {
36
+ "@types/node": "^26.4.1",
37
+ "typescript": "^7.0.2"
38
+ }
39
+ }