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.
Files changed (67) hide show
  1. package/README.txt +69 -0
  2. package/node-red-contrib-mysql-extended-test/mysql-extended.html +443 -0
  3. package/node-red-contrib-mysql-extended-test/mysql-extended.js +355 -0
  4. package/node-red-contrib-rfid-helper/rfid-lazy.html +153 -0
  5. package/node-red-contrib-rfid-helper/rfid-lazy.js +361 -0
  6. package/node-red-contrib-s7-plus/LICENSE +235 -0
  7. package/node-red-contrib-s7-plus/README.md +141 -0
  8. package/node-red-contrib-s7-plus/examples/read-multiple-values-flow.json +346 -0
  9. package/node-red-contrib-s7-plus/examples/read-write-test-flow.json +172 -0
  10. package/node-red-contrib-s7-plus/examples/write-single-values-flow.json +5583 -0
  11. package/node-red-contrib-s7-plus/lib/s7plus/browse/README.md +72 -0
  12. package/node-red-contrib-s7-plus/lib/s7plus/browse/areas.js +36 -0
  13. package/node-red-contrib-s7-plus/lib/s7plus/browse/datatypes.js +91 -0
  14. package/node-red-contrib-s7-plus/lib/s7plus/browse/flat-browser.js +433 -0
  15. package/node-red-contrib-s7-plus/lib/s7plus/browse/index.js +22 -0
  16. package/node-red-contrib-s7-plus/lib/s7plus/browse/lazy.js +397 -0
  17. package/node-red-contrib-s7-plus/lib/s7plus/browse/node-id.js +22 -0
  18. package/node-red-contrib-s7-plus/lib/s7plus/browse/resolve-symbolic.js +181 -0
  19. package/node-red-contrib-s7-plus/lib/s7plus/browse/vte-helpers.js +49 -0
  20. package/node-red-contrib-s7-plus/lib/s7plus/buffer-stream.js +96 -0
  21. package/node-red-contrib-s7-plus/lib/s7plus/client.js +1238 -0
  22. package/node-red-contrib-s7-plus/lib/s7plus/constants.js +155 -0
  23. package/node-red-contrib-s7-plus/lib/s7plus/crc/index.js +3 -0
  24. package/node-red-contrib-s7-plus/lib/s7plus/crc/s7-crc32.js +70 -0
  25. package/node-red-contrib-s7-plus/lib/s7plus/crc/symbol-crc.js +271 -0
  26. package/node-red-contrib-s7-plus/lib/s7plus/debug.js +88 -0
  27. package/node-red-contrib-s7-plus/lib/s7plus/explore-result.js +41 -0
  28. package/node-red-contrib-s7-plus/lib/s7plus/item-address.js +55 -0
  29. package/node-red-contrib-s7-plus/lib/s7plus/pdu-explore.js +77 -0
  30. package/node-red-contrib-s7-plus/lib/s7plus/pdu-messages.js +359 -0
  31. package/node-red-contrib-s7-plus/lib/s7plus/pobject.js +197 -0
  32. package/node-red-contrib-s7-plus/lib/s7plus/pvalue-codec.js +435 -0
  33. package/node-red-contrib-s7-plus/lib/s7plus/pvalue.js +653 -0
  34. package/node-red-contrib-s7-plus/lib/s7plus/pvar-lists.js +336 -0
  35. package/node-red-contrib-s7-plus/lib/s7plus/read-result.js +91 -0
  36. package/node-red-contrib-s7-plus/lib/s7plus/s7p.js +266 -0
  37. package/node-red-contrib-s7-plus/lib/s7plus/tag-routing.js +30 -0
  38. package/node-red-contrib-s7-plus/lib/s7plus/transport/cotp-stream.js +335 -0
  39. package/node-red-contrib-s7-plus/lib/s7plus/transport/s7-transport.js +335 -0
  40. package/node-red-contrib-s7-plus/nodes/icons/s7complus.png +0 -0
  41. package/node-red-contrib-s7-plus/nodes/icons/s7complus.svg +17 -0
  42. package/node-red-contrib-s7-plus/nodes/s7complus-endpoint.js +825 -0
  43. package/node-red-contrib-s7-plus/nodes/s7complus-explore.js +91 -0
  44. package/node-red-contrib-s7-plus/nodes/s7complus-in.js +255 -0
  45. package/node-red-contrib-s7-plus/nodes/s7complus-out.js +182 -0
  46. package/node-red-contrib-s7-plus/nodes/s7complus.html +1541 -0
  47. package/node-red-contrib-s7-plus/nodes/s7complus.js +10 -0
  48. package/node-red-contrib-s7-plus/package.json +50 -0
  49. package/node-red-contrib-s7-plus/plc/s7-1500/DB_Arrays.db +17 -0
  50. package/node-red-contrib-s7-plus/plc/s7-1500/DB_Binary.db +16 -0
  51. package/node-red-contrib-s7-plus/plc/s7-1500/DB_BitStrings.db +36 -0
  52. package/node-red-contrib-s7-plus/plc/s7-1500/DB_CharacterStrings.db +42 -0
  53. package/node-red-contrib-s7-plus/plc/s7-1500/DB_DateAndTime.db +70 -0
  54. package/node-red-contrib-s7-plus/plc/s7-1500/DB_FloatingPoint.db +42 -0
  55. package/node-red-contrib-s7-plus/plc/s7-1500/DB_Integers.db +72 -0
  56. package/node-red-contrib-s7-plus/plc/s7-1500/DB_Timers.db +40 -0
  57. package/node-red-contrib-s7-plus/scripts/example-flow-shared.js +39 -0
  58. package/node-red-contrib-s7-plus/scripts/generate-read-multiple-flow.js +331 -0
  59. package/node-red-contrib-s7-plus/scripts/generate-rw-test-flow.js +690 -0
  60. package/node-red-contrib-s7-plus/scripts/generate-write-flow.js +476 -0
  61. package/node-red-contrib-s7-plus/scripts/layout-write-flow.js +156 -0
  62. package/node-red-contrib-s7-plus/scripts/run-tests.js +16 -0
  63. package/node-red-flowgobal-change/variable-change.html +425 -0
  64. package/node-red-flowgobal-change/variable-change.js +92 -0
  65. package/node-red-ui_media_viewer/ui_media_viewer.html +87 -0
  66. package/node-red-ui_media_viewer/ui_media_viewer.js +136 -0
  67. 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,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = require('./symbol-crc');
@@ -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;