node-red-contrib-my-tools 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.
- package/README.txt +69 -0
- package/node-red-contrib-mysql-extended-test/mysql-extended.html +443 -0
- package/node-red-contrib-mysql-extended-test/mysql-extended.js +355 -0
- package/node-red-contrib-rfid-helper/rfid-lazy.html +153 -0
- package/node-red-contrib-rfid-helper/rfid-lazy.js +361 -0
- package/node-red-contrib-s7-plus/LICENSE +235 -0
- package/node-red-contrib-s7-plus/README.md +141 -0
- package/node-red-contrib-s7-plus/examples/read-multiple-values-flow.json +346 -0
- package/node-red-contrib-s7-plus/examples/read-write-test-flow.json +172 -0
- package/node-red-contrib-s7-plus/examples/write-single-values-flow.json +5583 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/README.md +72 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/areas.js +36 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/datatypes.js +91 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/flat-browser.js +433 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/index.js +22 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/lazy.js +397 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/node-id.js +22 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/resolve-symbolic.js +181 -0
- package/node-red-contrib-s7-plus/lib/s7plus/browse/vte-helpers.js +49 -0
- package/node-red-contrib-s7-plus/lib/s7plus/buffer-stream.js +96 -0
- package/node-red-contrib-s7-plus/lib/s7plus/client.js +1238 -0
- package/node-red-contrib-s7-plus/lib/s7plus/constants.js +155 -0
- package/node-red-contrib-s7-plus/lib/s7plus/crc/index.js +3 -0
- package/node-red-contrib-s7-plus/lib/s7plus/crc/s7-crc32.js +70 -0
- package/node-red-contrib-s7-plus/lib/s7plus/crc/symbol-crc.js +271 -0
- package/node-red-contrib-s7-plus/lib/s7plus/debug.js +88 -0
- package/node-red-contrib-s7-plus/lib/s7plus/explore-result.js +41 -0
- package/node-red-contrib-s7-plus/lib/s7plus/item-address.js +55 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pdu-explore.js +77 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pdu-messages.js +359 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pobject.js +197 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pvalue-codec.js +435 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pvalue.js +653 -0
- package/node-red-contrib-s7-plus/lib/s7plus/pvar-lists.js +336 -0
- package/node-red-contrib-s7-plus/lib/s7plus/read-result.js +91 -0
- package/node-red-contrib-s7-plus/lib/s7plus/s7p.js +266 -0
- package/node-red-contrib-s7-plus/lib/s7plus/tag-routing.js +30 -0
- package/node-red-contrib-s7-plus/lib/s7plus/transport/cotp-stream.js +335 -0
- package/node-red-contrib-s7-plus/lib/s7plus/transport/s7-transport.js +335 -0
- package/node-red-contrib-s7-plus/nodes/icons/s7complus.png +0 -0
- package/node-red-contrib-s7-plus/nodes/icons/s7complus.svg +17 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-endpoint.js +825 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-explore.js +91 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-in.js +255 -0
- package/node-red-contrib-s7-plus/nodes/s7complus-out.js +182 -0
- package/node-red-contrib-s7-plus/nodes/s7complus.html +1541 -0
- package/node-red-contrib-s7-plus/nodes/s7complus.js +10 -0
- package/node-red-contrib-s7-plus/package.json +50 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Arrays.db +17 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Binary.db +16 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_BitStrings.db +36 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_CharacterStrings.db +42 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_DateAndTime.db +70 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_FloatingPoint.db +42 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Integers.db +72 -0
- package/node-red-contrib-s7-plus/plc/s7-1500/DB_Timers.db +40 -0
- package/node-red-contrib-s7-plus/scripts/example-flow-shared.js +39 -0
- package/node-red-contrib-s7-plus/scripts/generate-read-multiple-flow.js +331 -0
- package/node-red-contrib-s7-plus/scripts/generate-rw-test-flow.js +690 -0
- package/node-red-contrib-s7-plus/scripts/generate-write-flow.js +476 -0
- package/node-red-contrib-s7-plus/scripts/layout-write-flow.js +156 -0
- package/node-red-contrib-s7-plus/scripts/run-tests.js +16 -0
- package/node-red-flowgobal-change/variable-change.html +425 -0
- package/node-red-flowgobal-change/variable-change.js +92 -0
- package/node-red-ui_media_viewer/ui_media_viewer.html +87 -0
- package/node-red-ui_media_viewer/ui_media_viewer.js +136 -0
- package/package.json +12 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Opcode = {
|
|
4
|
+
Request: 0x31,
|
|
5
|
+
Response: 0x32,
|
|
6
|
+
Notification: 0x33,
|
|
7
|
+
Response2: 0x02
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const Functioncode = {
|
|
11
|
+
Error: 0x04b1,
|
|
12
|
+
Explore: 0x04bb,
|
|
13
|
+
CreateObject: 0x04ca,
|
|
14
|
+
DeleteObject: 0x04d4,
|
|
15
|
+
SetVariable: 0x04f2,
|
|
16
|
+
GetVariable: 0x04fc,
|
|
17
|
+
SetMultiVariables: 0x0542,
|
|
18
|
+
GetMultiVariables: 0x054c,
|
|
19
|
+
GetVarSubStreamed: 0x0586,
|
|
20
|
+
InitSsl: 0x05b3
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const ProtocolVersion = {
|
|
24
|
+
V1: 0x01,
|
|
25
|
+
V2: 0x02,
|
|
26
|
+
V3: 0x03,
|
|
27
|
+
SystemEvent: 0xfe
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ElementID = {
|
|
31
|
+
StartOfObject: 0xa1,
|
|
32
|
+
TerminatingObject: 0xa2,
|
|
33
|
+
Attribute: 0xa3,
|
|
34
|
+
Relation: 0xa4,
|
|
35
|
+
VartypeList: 0xab,
|
|
36
|
+
VarnameList: 0xac
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const Datatype = {
|
|
40
|
+
Null: 0x00,
|
|
41
|
+
Bool: 0x01,
|
|
42
|
+
USInt: 0x02,
|
|
43
|
+
UInt: 0x03,
|
|
44
|
+
UDInt: 0x04,
|
|
45
|
+
ULInt: 0x05,
|
|
46
|
+
SInt: 0x06,
|
|
47
|
+
Int: 0x07,
|
|
48
|
+
DInt: 0x08,
|
|
49
|
+
LInt: 0x09,
|
|
50
|
+
Byte: 0x0a,
|
|
51
|
+
Word: 0x0b,
|
|
52
|
+
DWord: 0x0c,
|
|
53
|
+
LWord: 0x0d,
|
|
54
|
+
Real: 0x0e,
|
|
55
|
+
LReal: 0x0f,
|
|
56
|
+
Timestamp: 0x10,
|
|
57
|
+
Timespan: 0x11,
|
|
58
|
+
RID: 0x12,
|
|
59
|
+
AID: 0x13,
|
|
60
|
+
Blob: 0x14,
|
|
61
|
+
WString: 0x15,
|
|
62
|
+
Struct: 0x17
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const Ids = {
|
|
66
|
+
None: 0,
|
|
67
|
+
ObjectRoot: 201,
|
|
68
|
+
GetNewRIDOnServer: 211,
|
|
69
|
+
ObjectVariableTypeName: 233,
|
|
70
|
+
Block_BlockNumber: 2521,
|
|
71
|
+
ASObjectES_Comment: 4288,
|
|
72
|
+
NativeObjects_thePLCProgram_Rid: 3,
|
|
73
|
+
ClassSubscriptions: 255,
|
|
74
|
+
ClassOMSTypeInfoContainer: 534,
|
|
75
|
+
ObjectOMSTypeInfoContainer: 537,
|
|
76
|
+
TI_TComSize: 1502,
|
|
77
|
+
PLCProgram_Class_Rid: 2520,
|
|
78
|
+
DB_Class_Rid: 2574,
|
|
79
|
+
ClassServerSessionContainer: 284,
|
|
80
|
+
ObjectServerSessionContainer: 285,
|
|
81
|
+
ClassServerSession: 287,
|
|
82
|
+
ObjectNullServerSession: 288,
|
|
83
|
+
ServerSessionClientRID: 300,
|
|
84
|
+
ServerSessionRequest: 303,
|
|
85
|
+
ServerSessionResponse: 304,
|
|
86
|
+
ServerSessionVersion: 306,
|
|
87
|
+
LID_SessionVersionSystemPAOMString: 319,
|
|
88
|
+
SystemLimits: 1037,
|
|
89
|
+
Legitimate: 1846,
|
|
90
|
+
EffectiveProtectionLevel: 1842,
|
|
91
|
+
ObjectQualifier: 1256,
|
|
92
|
+
ParentRID: 1257,
|
|
93
|
+
CompositionAID: 1258,
|
|
94
|
+
KeyQualifier: 1259,
|
|
95
|
+
DB_ValueActual: 2550,
|
|
96
|
+
ControllerArea_ValueActual: 3736,
|
|
97
|
+
NativeObjects_theS7Timers_Rid: 84,
|
|
98
|
+
NativeObjects_theS7Counters_Rid: 83,
|
|
99
|
+
NativeObjects_theIArea_Rid: 80,
|
|
100
|
+
NativeObjects_theQArea_Rid: 81,
|
|
101
|
+
NativeObjects_theMArea_Rid: 82,
|
|
102
|
+
LID_LegitimationPayloadStruct: 40400,
|
|
103
|
+
LID_LegitimationPayloadType: 40401,
|
|
104
|
+
LID_LegitimationPayloadUsername: 40402,
|
|
105
|
+
LID_LegitimationPayloadPassword: 40403
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const AccessLevel = {
|
|
109
|
+
FullAccess: 1
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const S7Consts = {
|
|
113
|
+
errTCPConnectionTimeout: 0x00010000,
|
|
114
|
+
errTCPConnectionFailed: 0x00010001,
|
|
115
|
+
errTCPDataReceive: 0x00010004,
|
|
116
|
+
errTCPNotConnected: 0x00010007,
|
|
117
|
+
errIsoConnect: 0x00020000,
|
|
118
|
+
errIsoInvalidPDU: 0x00020001,
|
|
119
|
+
errOpenSSL: 0x00030000,
|
|
120
|
+
errCliInvalidParams: 0x00200000,
|
|
121
|
+
errCliAccessDenied: 0x0020000f,
|
|
122
|
+
errCliFirmwareNotSupported: 0x0020001a,
|
|
123
|
+
errCliDeviceNotSupported: 0x0020001b,
|
|
124
|
+
errCliNeedPassword: 0x0020000d
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
function errorText(code) {
|
|
128
|
+
const map = {
|
|
129
|
+
0: 'OK',
|
|
130
|
+
[S7Consts.errTCPConnectionTimeout]: 'TCP: Connection Timeout',
|
|
131
|
+
[S7Consts.errTCPConnectionFailed]: 'TCP: Connection Error',
|
|
132
|
+
[S7Consts.errTCPDataReceive]: 'TCP: Data receive Timeout',
|
|
133
|
+
[S7Consts.errTCPNotConnected]: 'CLI: Client not connected',
|
|
134
|
+
[S7Consts.errIsoConnect]: 'ISO: Connection Error',
|
|
135
|
+
[S7Consts.errIsoInvalidPDU]: 'ISO: Invalid PDU',
|
|
136
|
+
[S7Consts.errOpenSSL]: 'TLS/SSL: Error',
|
|
137
|
+
[S7Consts.errCliAccessDenied]: 'CPU: Access denied',
|
|
138
|
+
[S7Consts.errCliFirmwareNotSupported]: 'CLI: Firmware not supported',
|
|
139
|
+
[S7Consts.errCliDeviceNotSupported]: 'CLI: Device not supported',
|
|
140
|
+
[S7Consts.errCliNeedPassword]: 'CPU: Password required'
|
|
141
|
+
};
|
|
142
|
+
return map[code] || `CLI: Unknown error (0x${(code >>> 0).toString(16)})`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
Opcode,
|
|
147
|
+
Functioncode,
|
|
148
|
+
ProtocolVersion,
|
|
149
|
+
ElementID,
|
|
150
|
+
Datatype,
|
|
151
|
+
Ids,
|
|
152
|
+
AccessLevel,
|
|
153
|
+
S7Consts,
|
|
154
|
+
errorText
|
|
155
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* S7CommPlus CRC32 - used for SymbolCRC in ItemAddress of read/write requests.
|
|
5
|
+
*
|
|
6
|
+
* Polynomial: 0xF4ACFB13 (MSB-first, non-reflected)
|
|
7
|
+
* Init: 0, Final XOR: none
|
|
8
|
+
*
|
|
9
|
+
* Informed by HarpoS7 (https://github.com/bonk-dev/HarpoS7, Copyright (c) 2024 bonk, MIT).
|
|
10
|
+
* Wireshark S7CommPlus dissector used as an additional protocol reference.
|
|
11
|
+
*
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const POLY = 0xF4ACFB13;
|
|
15
|
+
|
|
16
|
+
const TABLE = new Uint32Array(256);
|
|
17
|
+
for (let i = 0; i < 256; i++) {
|
|
18
|
+
let crc = (i << 24) >>> 0;
|
|
19
|
+
for (let j = 0; j < 8; j++) {
|
|
20
|
+
if (crc & 0x80000000) {
|
|
21
|
+
crc = ((crc << 1) ^ POLY) >>> 0;
|
|
22
|
+
} else {
|
|
23
|
+
crc = (crc << 1) >>> 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
TABLE[i] = crc >>> 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class S7CRC32 {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.state = 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get result() {
|
|
35
|
+
return this.state >>> 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
updateByte(b) {
|
|
39
|
+
this.state = (TABLE[(b ^ (this.state >>> 24)) & 0xFF] ^ ((this.state << 8) >>> 0)) >>> 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
update(input) {
|
|
43
|
+
// Symbol/member names must be hashed over their UTF-8 byte
|
|
44
|
+
// representation, exactly as the PLC stores and transmits them
|
|
45
|
+
// (PVarnameList uses a byte-length prefix + UTF-8 payload). Using
|
|
46
|
+
// latin1 here would produce a wrong SymbolCRC for any non-ASCII
|
|
47
|
+
// name (e.g. an umlaut "ä" is 0xC3 0xA4 in UTF-8 vs. 0xE4 in
|
|
48
|
+
// latin1), which the PLC rejects with a CRC-mismatch read error.
|
|
49
|
+
const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : input;
|
|
50
|
+
for (let i = 0; i < buf.length; i++) {
|
|
51
|
+
this.updateByte(buf[i]);
|
|
52
|
+
}
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
updateUInt32LE(value) {
|
|
57
|
+
this.updateByte(value & 0xFF);
|
|
58
|
+
this.updateByte((value >>> 8) & 0xFF);
|
|
59
|
+
this.updateByte((value >>> 16) & 0xFF);
|
|
60
|
+
this.updateByte((value >>> 24) & 0xFF);
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
reset() {
|
|
65
|
+
this.state = 0;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { S7CRC32, TABLE, POLY };
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute the ItemAddress SymbolCRC for S7CommPlus read/write requests.
|
|
5
|
+
*
|
|
6
|
+
* The CRC identifies a symbol path so the PLC can verify the client
|
|
7
|
+
* is accessing the intended symbol even if LIDs have shifted after
|
|
8
|
+
* a program download.
|
|
9
|
+
*
|
|
10
|
+
* Algorithm (verified against S7-1500 FW):
|
|
11
|
+
* 1. innerCrc = CRC(memberName + typeInfo)
|
|
12
|
+
* 2. result = new CRC().updateUInt32LE(innerCrc)
|
|
13
|
+
*
|
|
14
|
+
* TypeInfo encoding (appended to member name):
|
|
15
|
+
* - Simple types: 1 byte TypeCode
|
|
16
|
+
* - Arrays: 0x10 + elementTypeCode(1) + lowerBound(4 LE)
|
|
17
|
+
* - Structs: 0x11
|
|
18
|
+
*
|
|
19
|
+
* For nested struct members the CRC chains hierarchically:
|
|
20
|
+
* parentCrc → updateUInt32LE(parentCrc) → updateByte(0x89) → updateUInt32LE(childCrc)
|
|
21
|
+
*
|
|
22
|
+
* TypeCodes (S7-1500, scope Default):
|
|
23
|
+
* Bool=0x01, Byte=0x02, Char=0x03, Word=0x04, Int=0x05, DWord=0x06,
|
|
24
|
+
* DInt=0x07, Real=0x08, Date=0x09, TimeOfDay=0x0A, Time=0x0B,
|
|
25
|
+
* S5Time=0x0C, DateAndTime=0x0E, Array=0x10, Struct=0x11, String=0x13,
|
|
26
|
+
* LReal=0x30, ULInt=0x31, LInt=0x32, LWord=0x33, USInt=0x34, UInt=0x35,
|
|
27
|
+
* UDInt=0x36, SInt=0x37, WChar=0x3D, WString=0x3E, LTime=0x40,
|
|
28
|
+
* LTimeOfDay=0x41
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const { S7CRC32 } = require('./s7-crc32');
|
|
32
|
+
|
|
33
|
+
const STRUCT_CHILD_DELIMITER = 0x89;
|
|
34
|
+
|
|
35
|
+
const TypeCode = Object.freeze({
|
|
36
|
+
Bool: 0x01,
|
|
37
|
+
Byte: 0x02,
|
|
38
|
+
Char: 0x03,
|
|
39
|
+
Word: 0x04,
|
|
40
|
+
Int: 0x05,
|
|
41
|
+
DWord: 0x06,
|
|
42
|
+
DInt: 0x07,
|
|
43
|
+
Real: 0x08,
|
|
44
|
+
Date: 0x09,
|
|
45
|
+
TimeOfDay: 0x0A,
|
|
46
|
+
Time: 0x0B,
|
|
47
|
+
S5Time: 0x0C,
|
|
48
|
+
DateAndTime: 0x0E,
|
|
49
|
+
Array: 0x10,
|
|
50
|
+
Struct: 0x11,
|
|
51
|
+
String: 0x13,
|
|
52
|
+
LReal: 0x30,
|
|
53
|
+
ULInt: 0x31,
|
|
54
|
+
LInt: 0x32,
|
|
55
|
+
LWord: 0x33,
|
|
56
|
+
USInt: 0x34,
|
|
57
|
+
UInt: 0x35,
|
|
58
|
+
UDInt: 0x36,
|
|
59
|
+
SInt: 0x37,
|
|
60
|
+
WChar: 0x3D,
|
|
61
|
+
WString: 0x3E,
|
|
62
|
+
LTime: 0x40,
|
|
63
|
+
LTimeOfDay: 0x41
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Map softdatatype (from PVartypeListElement) → TypeCode for CRC computation.
|
|
68
|
+
* The softdatatype values used by the S7-1500 firmware in TypeInfo responses
|
|
69
|
+
* happen to align with the CRC TypeCodes for most types.
|
|
70
|
+
*/
|
|
71
|
+
const SOFTDATATYPE_TO_TYPECODE = Object.freeze({
|
|
72
|
+
1: TypeCode.Bool,
|
|
73
|
+
2: TypeCode.Byte,
|
|
74
|
+
3: TypeCode.Char,
|
|
75
|
+
4: TypeCode.Word,
|
|
76
|
+
5: TypeCode.Int,
|
|
77
|
+
6: TypeCode.DWord,
|
|
78
|
+
7: TypeCode.DInt,
|
|
79
|
+
8: TypeCode.Real,
|
|
80
|
+
9: TypeCode.Date,
|
|
81
|
+
10: TypeCode.TimeOfDay,
|
|
82
|
+
11: TypeCode.Time,
|
|
83
|
+
12: TypeCode.S5Time,
|
|
84
|
+
14: TypeCode.DateAndTime,
|
|
85
|
+
17: TypeCode.Struct,
|
|
86
|
+
19: TypeCode.String,
|
|
87
|
+
40: TypeCode.Bool,
|
|
88
|
+
48: TypeCode.LReal,
|
|
89
|
+
49: TypeCode.ULInt,
|
|
90
|
+
50: TypeCode.LInt,
|
|
91
|
+
51: TypeCode.LWord,
|
|
92
|
+
52: TypeCode.USInt,
|
|
93
|
+
53: TypeCode.UInt,
|
|
94
|
+
54: TypeCode.UDInt,
|
|
95
|
+
55: TypeCode.SInt,
|
|
96
|
+
61: TypeCode.WChar,
|
|
97
|
+
62: TypeCode.WString,
|
|
98
|
+
64: TypeCode.LTime,
|
|
99
|
+
65: TypeCode.LTimeOfDay,
|
|
100
|
+
|
|
101
|
+
// Hardware / system softdatatypes → CRC TypeCode of underlying storage type
|
|
102
|
+
// 32-bit types (DWord): AOM_IDENT, EVENT_*
|
|
103
|
+
128: TypeCode.DWord, // AOM_IDENT
|
|
104
|
+
129: TypeCode.DWord, // EVENT_ANY
|
|
105
|
+
130: TypeCode.DWord, // EVENT_ATT
|
|
106
|
+
131: TypeCode.DWord, // EVENT_HWINT
|
|
107
|
+
132: TypeCode.DWord, // FOLDER
|
|
108
|
+
133: TypeCode.DWord, // AOM_AID
|
|
109
|
+
134: TypeCode.DWord, // AOM_LINK
|
|
110
|
+
|
|
111
|
+
// 16-bit Word types: HW_*, CONN_ANY/PRG/OUC
|
|
112
|
+
144: TypeCode.Word, // HW_ANY
|
|
113
|
+
145: TypeCode.Word, // HW_IOSYSTEM
|
|
114
|
+
146: TypeCode.Word, // HW_DPMASTER
|
|
115
|
+
147: TypeCode.Word, // HW_DEVICE
|
|
116
|
+
148: TypeCode.Word, // HW_DPSLAVE
|
|
117
|
+
149: TypeCode.Word, // HW_IO
|
|
118
|
+
150: TypeCode.Word, // HW_MODULE
|
|
119
|
+
151: TypeCode.Word, // HW_SUBMODULE
|
|
120
|
+
152: TypeCode.Word, // HW_HSC
|
|
121
|
+
153: TypeCode.Word, // HW_PWM
|
|
122
|
+
154: TypeCode.Word, // HW_PTO
|
|
123
|
+
155: TypeCode.Word, // HW_INTERFACE
|
|
124
|
+
156: TypeCode.Word, // HW_IEPORT
|
|
125
|
+
168: TypeCode.Word, // CONN_ANY
|
|
126
|
+
169: TypeCode.Word, // CONN_PRG
|
|
127
|
+
170: TypeCode.Word, // CONN_OUC
|
|
128
|
+
171: TypeCode.DWord, // CONN_R_ID (32-bit)
|
|
129
|
+
|
|
130
|
+
// 16-bit Int types: OB_* (OB numbers are signed Int per TIA Portal)
|
|
131
|
+
160: TypeCode.Int, // OB_ANY
|
|
132
|
+
161: TypeCode.Int, // OB_DELAY
|
|
133
|
+
162: TypeCode.Int, // OB_TOD
|
|
134
|
+
163: TypeCode.Int, // OB_CYCLIC
|
|
135
|
+
164: TypeCode.Int, // OB_ATT
|
|
136
|
+
192: TypeCode.Int, // OB_PCYCLE
|
|
137
|
+
193: TypeCode.Int, // OB_HWINT
|
|
138
|
+
195: TypeCode.Int, // OB_DIAG
|
|
139
|
+
196: TypeCode.Int, // OB_TIMEERROR
|
|
140
|
+
197: TypeCode.Int, // OB_STARTUP
|
|
141
|
+
|
|
142
|
+
// 16-bit UInt types: DB_*, PORT, RTM, PIP
|
|
143
|
+
173: TypeCode.UInt, // PORT
|
|
144
|
+
174: TypeCode.UInt, // RTM
|
|
145
|
+
175: TypeCode.UInt, // PIP
|
|
146
|
+
208: TypeCode.UInt, // DB_ANY
|
|
147
|
+
209: TypeCode.UInt, // DB_WWW
|
|
148
|
+
210: TypeCode.UInt // DB_DYN
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
function softdatatypeToTypeCode(sd) {
|
|
152
|
+
return SOFTDATATYPE_TO_TYPECODE[sd] || sd;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Compute the inner CRC for a single member (name + type info).
|
|
157
|
+
* @param {string} name - member name (UTF-8)
|
|
158
|
+
* @param {number} typeCode - TypeCode byte
|
|
159
|
+
* @param {object} [arrayInfo] - { elementTypeCode, lowerBound } for arrays
|
|
160
|
+
* @returns {number} raw CRC (before double-hash)
|
|
161
|
+
*/
|
|
162
|
+
function memberInnerCrc(name, typeCode, arrayInfo) {
|
|
163
|
+
const crc = new S7CRC32();
|
|
164
|
+
crc.update(name);
|
|
165
|
+
if (typeCode === TypeCode.Array && arrayInfo) {
|
|
166
|
+
crc.updateByte(TypeCode.Array);
|
|
167
|
+
crc.updateByte(arrayInfo.elementTypeCode & 0xFF);
|
|
168
|
+
const lb = Buffer.alloc(4);
|
|
169
|
+
lb.writeInt32LE(arrayInfo.lowerBound || 0);
|
|
170
|
+
crc.update(lb);
|
|
171
|
+
} else {
|
|
172
|
+
crc.updateByte(typeCode & 0xFF);
|
|
173
|
+
}
|
|
174
|
+
return crc.result;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Double-hash: wrap an inner CRC into the final ItemAddress CRC format.
|
|
179
|
+
* @param {number} innerCrc
|
|
180
|
+
* @returns {number}
|
|
181
|
+
*/
|
|
182
|
+
function finalizeItemCrc(innerCrc) {
|
|
183
|
+
const crc = new S7CRC32();
|
|
184
|
+
crc.updateUInt32LE(innerCrc);
|
|
185
|
+
return crc.result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Compute ItemAddress SymbolCRC for a direct DB member (no struct nesting).
|
|
190
|
+
* @param {string} name - member name
|
|
191
|
+
* @param {number} typeCode - TypeCode byte
|
|
192
|
+
* @param {object} [arrayInfo] - { elementTypeCode, lowerBound }
|
|
193
|
+
* @returns {number}
|
|
194
|
+
*/
|
|
195
|
+
function computeItemCrc(name, typeCode, arrayInfo) {
|
|
196
|
+
return finalizeItemCrc(memberInnerCrc(name, typeCode, arrayInfo));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Compute ItemAddress SymbolCRC for a struct-nested member.
|
|
201
|
+
* @param {Array<{name: string, typeCode: number, arrayInfo?: object}>} pathSegments
|
|
202
|
+
* Ordered from outermost struct to leaf member.
|
|
203
|
+
* @returns {number}
|
|
204
|
+
*/
|
|
205
|
+
function computeNestedItemCrc(pathSegments) {
|
|
206
|
+
if (pathSegments.length === 0) return 0;
|
|
207
|
+
if (pathSegments.length === 1) {
|
|
208
|
+
const s = pathSegments[0];
|
|
209
|
+
return computeItemCrc(s.name, s.typeCode, s.arrayInfo);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const first = pathSegments[0];
|
|
213
|
+
const crc = new S7CRC32();
|
|
214
|
+
crc.updateUInt32LE(memberInnerCrc(first.name, first.typeCode, first.arrayInfo));
|
|
215
|
+
|
|
216
|
+
for (let i = 1; i < pathSegments.length; i++) {
|
|
217
|
+
crc.updateByte(STRUCT_CHILD_DELIMITER);
|
|
218
|
+
const seg = pathSegments[i];
|
|
219
|
+
crc.updateUInt32LE(memberInnerCrc(seg.name, seg.typeCode, seg.arrayInfo));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return crc.result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Compute ItemAddress SymbolCRC directly from browse crcMeta.
|
|
227
|
+
* Supports single members, arrays, and nested struct paths.
|
|
228
|
+
* @param {object} crcMeta - as returned by resolveLeaf().crcMeta
|
|
229
|
+
* @returns {number}
|
|
230
|
+
*/
|
|
231
|
+
function computeCrcFromMeta(crcMeta) {
|
|
232
|
+
if (!crcMeta) return 0;
|
|
233
|
+
|
|
234
|
+
if (crcMeta.pathSegments) {
|
|
235
|
+
return computeNestedItemCrc(crcMeta.pathSegments.map(seg => {
|
|
236
|
+
if (seg.isArray) {
|
|
237
|
+
return {
|
|
238
|
+
name: seg.memberName,
|
|
239
|
+
typeCode: TypeCode.Array,
|
|
240
|
+
arrayInfo: {
|
|
241
|
+
elementTypeCode: softdatatypeToTypeCode(seg.elementSoftdatatype),
|
|
242
|
+
lowerBound: seg.lowerBound || 0
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
return { name: seg.memberName, typeCode: softdatatypeToTypeCode(seg.softdatatype) };
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!crcMeta.memberName) return 0;
|
|
251
|
+
if (crcMeta.isArray) {
|
|
252
|
+
return computeItemCrc(crcMeta.memberName, TypeCode.Array, {
|
|
253
|
+
elementTypeCode: softdatatypeToTypeCode(crcMeta.elementSoftdatatype),
|
|
254
|
+
lowerBound: crcMeta.lowerBound || 0
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const tc = softdatatypeToTypeCode(crcMeta.softdatatype);
|
|
258
|
+
return computeItemCrc(crcMeta.memberName, tc);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = {
|
|
262
|
+
S7CRC32,
|
|
263
|
+
TypeCode,
|
|
264
|
+
STRUCT_CHILD_DELIMITER,
|
|
265
|
+
softdatatypeToTypeCode,
|
|
266
|
+
memberInnerCrc,
|
|
267
|
+
finalizeItemCrc,
|
|
268
|
+
computeItemCrc,
|
|
269
|
+
computeNestedItemCrc,
|
|
270
|
+
computeCrcFromMeta
|
|
271
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tiny scope-aware debug logger.
|
|
5
|
+
*
|
|
6
|
+
* Activation:
|
|
7
|
+
* - environment variable `S7P_DEBUG`:
|
|
8
|
+
* `1` / `true` / `*` enable all scopes
|
|
9
|
+
* `client,endpoint` enable only listed scopes (comma-separated)
|
|
10
|
+
* - programmatic: `require('./debug').setEnabled('client,transport')`
|
|
11
|
+
*
|
|
12
|
+
* Output goes to stderr via `console.error` so it surfaces in the Node-RED
|
|
13
|
+
* log without interfering with stdout-based protocols.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const SCOPE_PREFIX = 's7p';
|
|
17
|
+
let enabledScopes = parseScopes(process.env.S7P_DEBUG || '');
|
|
18
|
+
let sink = (line) => { try { console.error(line); } catch { /* ignore */ } };
|
|
19
|
+
|
|
20
|
+
function parseScopes(spec) {
|
|
21
|
+
if (!spec) return new Set();
|
|
22
|
+
const s = String(spec).trim().toLowerCase();
|
|
23
|
+
if (s === '0' || s === 'false' || s === 'off') return new Set();
|
|
24
|
+
if (s === '1' || s === 'true' || s === '*' || s === 'all') return new Set(['*']);
|
|
25
|
+
return new Set(s.split(',').map(x => x.trim()).filter(Boolean));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function setEnabled(spec) {
|
|
29
|
+
enabledScopes = parseScopes(spec);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isEnabled(scope) {
|
|
33
|
+
if (!enabledScopes.size) return false;
|
|
34
|
+
if (enabledScopes.has('*')) return true;
|
|
35
|
+
return enabledScopes.has(String(scope).toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function setSink(fn) {
|
|
39
|
+
sink = typeof fn === 'function' ? fn : sink;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ts() {
|
|
43
|
+
const d = new Date();
|
|
44
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
45
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
46
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
47
|
+
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
48
|
+
return `${hh}:${mm}:${ss}.${ms}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fmtValue(v) {
|
|
52
|
+
if (v == null) return String(v);
|
|
53
|
+
if (typeof v === 'bigint') return `${v}n`;
|
|
54
|
+
if (typeof v === 'number') return Number.isFinite(v) ? String(v) : 'NaN';
|
|
55
|
+
if (typeof v === 'string') return v;
|
|
56
|
+
if (Buffer.isBuffer(v)) return `<Buffer ${v.length}b ${v.subarray(0, 16).toString('hex')}${v.length > 16 ? '…' : ''}>`;
|
|
57
|
+
if (v instanceof Error) return v.message;
|
|
58
|
+
try { return JSON.stringify(v); } catch { return String(v); }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Lazily resolve a debug record so we never pay the formatting cost when
|
|
63
|
+
* the scope is disabled. Accepts either positional args or a thunk:
|
|
64
|
+
* debug('client', 'connect', { host });
|
|
65
|
+
* debug('client', () => ['connect', { host }]);
|
|
66
|
+
*/
|
|
67
|
+
function debug(scope, ...args) {
|
|
68
|
+
if (!isEnabled(scope)) return;
|
|
69
|
+
let parts = args;
|
|
70
|
+
if (parts.length === 1 && typeof parts[0] === 'function') {
|
|
71
|
+
try { parts = parts[0]() || []; } catch (e) { parts = [`<debug-thunk-error: ${e.message}>`]; }
|
|
72
|
+
if (!Array.isArray(parts)) parts = [parts];
|
|
73
|
+
}
|
|
74
|
+
const head = `[${SCOPE_PREFIX}:${scope}] ${ts()}`;
|
|
75
|
+
const tail = parts.map(fmtValue).join(' ');
|
|
76
|
+
sink(`${head} ${tail}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Convenience scoped logger:
|
|
81
|
+
* const log = require('./debug').scoped('client');
|
|
82
|
+
* log('connect', { host });
|
|
83
|
+
*/
|
|
84
|
+
function scoped(scope) {
|
|
85
|
+
return (...args) => debug(scope, ...args);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { debug, scoped, setEnabled, isEnabled, setSink };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const VALID_SYMBOL_INFOS = new Set(['none', 'object', 'array']);
|
|
4
|
+
|
|
5
|
+
function normalizeSymbolInfos(mode) {
|
|
6
|
+
if (typeof mode === 'string' && VALID_SYMBOL_INFOS.has(mode)) return mode;
|
|
7
|
+
return 'none';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function stripSymbolName(entry) {
|
|
11
|
+
const { name, ...info } = entry;
|
|
12
|
+
return info;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatExplorePayload(rawSymbols, symbolInfos) {
|
|
16
|
+
const mode = normalizeSymbolInfos(symbolInfos);
|
|
17
|
+
const symbols = rawSymbols.map(s => s.name);
|
|
18
|
+
|
|
19
|
+
if (mode === 'none') {
|
|
20
|
+
return { symbols };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (mode === 'object') {
|
|
24
|
+
const infos = {};
|
|
25
|
+
for (const entry of rawSymbols) {
|
|
26
|
+
infos[entry.name] = stripSymbolName(entry);
|
|
27
|
+
}
|
|
28
|
+
return { symbols, infos };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const infos = rawSymbols.map(entry => ({
|
|
32
|
+
symbol: entry.name,
|
|
33
|
+
...stripSymbolName(entry)
|
|
34
|
+
}));
|
|
35
|
+
return { symbols, infos };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
normalizeSymbolInfos,
|
|
40
|
+
formatExplorePayload
|
|
41
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const S7p = require('./s7p');
|
|
4
|
+
const { Ids } = require('./constants');
|
|
5
|
+
|
|
6
|
+
class ItemAddress {
|
|
7
|
+
constructor(accessAreaOrString, accessSubArea) {
|
|
8
|
+
this.symbolCrc = 0;
|
|
9
|
+
this.lid = [];
|
|
10
|
+
if (typeof accessAreaOrString === 'string') {
|
|
11
|
+
const parts = accessAreaOrString.split('.');
|
|
12
|
+
const ids = parts.map(p => parseInt(p, 16));
|
|
13
|
+
this.accessArea = ids[0];
|
|
14
|
+
if (this.accessArea >= 0x8a0e0000) {
|
|
15
|
+
this.accessSubArea = Ids.DB_ValueActual;
|
|
16
|
+
} else if ([
|
|
17
|
+
Ids.NativeObjects_theS7Timers_Rid,
|
|
18
|
+
Ids.NativeObjects_theS7Counters_Rid,
|
|
19
|
+
Ids.NativeObjects_theIArea_Rid,
|
|
20
|
+
Ids.NativeObjects_theQArea_Rid,
|
|
21
|
+
Ids.NativeObjects_theMArea_Rid
|
|
22
|
+
].includes(this.accessArea)) {
|
|
23
|
+
this.accessSubArea = Ids.ControllerArea_ValueActual;
|
|
24
|
+
} else {
|
|
25
|
+
this.accessSubArea = accessSubArea ?? Ids.DB_ValueActual;
|
|
26
|
+
}
|
|
27
|
+
for (let i = 1; i < ids.length; i++) this.lid.push(ids[i]);
|
|
28
|
+
} else {
|
|
29
|
+
this.accessArea = accessAreaOrString ?? 0;
|
|
30
|
+
this.accessSubArea = accessSubArea ?? Ids.DB_ValueActual;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getNumberOfFields() {
|
|
35
|
+
return 4 + this.lid.length;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
serialize(buf) {
|
|
39
|
+
let n = 0;
|
|
40
|
+
n += S7p.encodeUInt32Vlq(buf, this.symbolCrc);
|
|
41
|
+
n += S7p.encodeUInt32Vlq(buf, this.accessArea >>> 0);
|
|
42
|
+
n += S7p.encodeUInt32Vlq(buf, this.lid.length + 1);
|
|
43
|
+
n += S7p.encodeUInt32Vlq(buf, this.accessSubArea >>> 0);
|
|
44
|
+
for (const id of this.lid) n += S7p.encodeUInt32Vlq(buf, id >>> 0);
|
|
45
|
+
return n;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getAccessString() {
|
|
49
|
+
let s = this.accessArea.toString(16).toUpperCase();
|
|
50
|
+
for (const i of this.lid) s += `.${i.toString(16).toUpperCase()}`;
|
|
51
|
+
return s;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = ItemAddress;
|