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,72 @@
1
+ # Lazy PLC browse
2
+
3
+ ## Protocol
4
+
5
+ Mirrors the C# `S7CommPlusDriver` reference (`GetTypeInformation`, `GetCommentsXml`):
6
+
7
+ 1. **Roots**: `Explore { exploreId = NativeObjects_thePLCProgram_Rid, exploreRequestId = None, recursive = 1 }`
8
+ with `addressList = [ObjectVariableTypeName]` (DB number from `relationId`; comment not needed),
9
+ then read LID=1 on every DB to get `db_block_ti_relid`.
10
+ 2. **Children** (on demand for one block/struct):
11
+ `Explore { exploreId = tiRelId, exploreRequestId = None, recursive = 1 }`.
12
+ Returned objects are cached by `relationId` so nested structs reuse the same subtree.
13
+ 3. **Arrays** are virtual: page nodes (`[0..31]`, `[32..63]`, …) are generated client-side
14
+ from the array length; struct-array elements lazy-explore via the relation id.
15
+
16
+ ## Editor
17
+
18
+ - `POST /s7complus/browse/roots` → `{ nodes, browseSessionId? }`
19
+ - `POST /s7complus/browse/children` → `{ nodes }` (body: `nodeId`, session/endpoint context)
20
+ - `POST /s7complus/browse/resolve` → `{ name, address, datatype }`
21
+ - `POST /s7complus/browse` → **410 deprecated**
22
+
23
+ Ephemeral browse (before deploy) uses `browseSessionId` from roots on subsequent calls.
24
+
25
+ ## Stale-connection recovery
26
+
27
+ A "live" S7+ TCP connection often dies silently between two browse calls
28
+ (NAT idle expiry, PLC reboot, cable, …). The recovery rules are:
29
+
30
+ 1. `S7CommPlusClient._waitForPdu` fires its read timeout (default 5s).
31
+ The handler **proactively tears the transport down** and calls
32
+ `_onTransportClosed({ reason: 'pdu-read-timeout' })`. The client emits
33
+ `disconnect` once and flips `connected` to `false` — so the very next
34
+ request does NOT pay another full read-timeout window.
35
+ 2. `forceDisconnect(reason)` (new) tears the transport down **without**
36
+ sending `DeleteObject`. Use this whenever the socket is already known
37
+ stale; the graceful `disconnect()` is reserved for the live path.
38
+ 3. `nodes/s7complus-endpoint.js` uses `forceDisconnect` in its reconnect
39
+ path (`ensureConnected(true)`) and applies a single retry both at the
40
+ endpoint level (`withReconnect`) and at the HTTP handler level
41
+ (`handleBrowseRequest`) for ephemeral browse sessions.
42
+ 4. Ephemeral browse sessions listen to the client's `disconnect` event
43
+ and mark themselves `dead`; the next `getEphemeralSession` lookup
44
+ discards them and the HTTP handler reconnects automatically.
45
+
46
+ ## Debug logging
47
+
48
+ Set `S7P_DEBUG=*` (all scopes) or `S7P_DEBUG=client,endpoint,transport`
49
+ (comma list) before starting Node-RED to surface every connect,
50
+ disconnect, PDU send/receive and browse retry on stderr. Output is
51
+ prefixed with `[s7p:<scope>] HH:MM:SS.mmm`.
52
+
53
+ ## Live tests
54
+
55
+ All scripts require an explicit PLC host (no built-in default). Optional env vars:
56
+ `S7_HOST`, `S7_PORT`, `S7_PASSWORD`, `S7_SYMBOL`, `S7_SYMBOLS` (comma-separated),
57
+ `S7_DB_PREFIX`, `S7_WRITE_MARKER`.
58
+
59
+ - `node test/scripts/live-browse.js <host> [port] [password]` — full browse chain
60
+ - `node test/scripts/live-reconnect.js <host> [port] [password] [timeoutMs]` —
61
+ stale-socket / idle reconnect scenario; verifies pass 2 finishes in
62
+ `~timeout + connect time` (not `2 × timeout`)
63
+ - `node test/scripts/live-crc-verify.js <host> [port] [password] <symbol> [...]` —
64
+ read symbolic symbols with computed CRC
65
+ - `node test/scripts/live-crc-test.js <host> [port] [password] <arraySymbol>` —
66
+ compare PLC vte.symbolCrc with locally computed CRC variants
67
+ - `node test/scripts/live-array-crc-diagnose.js <host> [port] [password] <symbol> [...]` —
68
+ diagnose CRC/address variants for array elements
69
+ - `node test/scripts/live-datatype-rw.js <host> [port] [password]` —
70
+ read/type-check and write/readback sweep over DB blocks
71
+
72
+ Example: `node test/scripts/live-browse.js 192.168.0.1 102`
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const { Ids } = require('../constants');
4
+
5
+ const MEMORY_AREAS = [
6
+ { name: 'IArea', label: 'IArea (Inputs)', accessId: Ids.NativeObjects_theIArea_Rid, tiRelId: 0x90010000 },
7
+ { name: 'QArea', label: 'QArea (Outputs)', accessId: Ids.NativeObjects_theQArea_Rid, tiRelId: 0x90020000 },
8
+ { name: 'MArea', label: 'MArea (Merker)', accessId: Ids.NativeObjects_theMArea_Rid, tiRelId: 0x90030000 },
9
+ { name: 'S7Timers', label: 'S7Timers (Timers)', accessId: Ids.NativeObjects_theS7Timers_Rid, tiRelId: 0x90050000 },
10
+ { name: 'S7Counters', label: 'S7Counters (Counters)', accessId: Ids.NativeObjects_theS7Counters_Rid, tiRelId: 0x90060000 }
11
+ ];
12
+
13
+ const MEMORY_AREA_NAMES = MEMORY_AREAS.map(a => a.name);
14
+
15
+ /**
16
+ * Normalize explore scope: empty partial selection falls back to everything.
17
+ * @param {object} [scope]
18
+ * @returns {{ everything: boolean, dbs: string[], areas: string[] }}
19
+ */
20
+ function normalizeExploreScope(scope) {
21
+ if (!scope || scope.everything) {
22
+ return { everything: true, dbs: [], areas: [] };
23
+ }
24
+ const dbs = Array.isArray(scope.dbs) ? scope.dbs.filter(Boolean) : [];
25
+ const areas = Array.isArray(scope.areas) ? scope.areas.filter(Boolean) : [];
26
+ if (dbs.length === 0 && areas.length === 0) {
27
+ return { everything: true, dbs: [], areas: [] };
28
+ }
29
+ return { everything: false, dbs, areas };
30
+ }
31
+
32
+ module.exports = {
33
+ MEMORY_AREAS,
34
+ MEMORY_AREA_NAMES,
35
+ normalizeExploreScope
36
+ };
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ const eNodeType = { Root: 1, Var: 2, Array: 3, StructArray: 4 };
4
+
5
+ // Primitive + hardware/S7 names (Wireshark tagdescr_softdatatype_names + vendor-specific IDs)
6
+ const SOFTDATATYPE_NAMES = {
7
+ 1: 'Bool', 2: 'Byte', 3: 'Char', 4: 'Word', 5: 'Int', 6: 'DWord', 7: 'DInt', 8: 'Real',
8
+ 9: 'Date', 10: 'TimeOfDay', 11: 'Time', 12: 'S5Time', 14: 'DateAndTime', 17: 'Struct',
9
+ 19: 'String', 20: 'Pointer', 22: 'Any', 23: 'BlockFb', 24: 'BlockFc', 28: 'Counter',
10
+ 29: 'Timer', 40: 'BBool', 48: 'LReal', 49: 'ULInt', 50: 'LInt', 51: 'LWord', 52: 'USInt',
11
+ 53: 'UInt', 54: 'UDInt', 55: 'SInt', 61: 'WChar', 62: 'WString', 64: 'LTime', 65: 'LTod',
12
+ 66: 'Ldt', 67: 'Dtl',
13
+ 96: 'REMOTE',
14
+ 128: 'AOM_IDENT', 129: 'EVENT_ANY', 130: 'EVENT_ATT', 131: 'EVENT_HWINT',
15
+ 132: 'FOLDER', 133: 'AOM_AID', 134: 'AOM_LINK',
16
+ 144: 'HW_ANY', 145: 'HW_IOSYSTEM', 146: 'HW_DPMASTER', 147: 'HW_DEVICE', 148: 'HW_DPSLAVE',
17
+ 149: 'HW_IO', 150: 'HW_MODULE', 151: 'HW_SUBMODULE', 152: 'HW_HSC', 153: 'HW_PWM',
18
+ 154: 'HW_PTO', 155: 'HW_INTERFACE', 156: 'HW_IEPORT',
19
+ 160: 'OB_ANY', 161: 'OB_DELAY', 162: 'OB_TOD', 163: 'OB_CYCLIC', 164: 'OB_ATT',
20
+ 168: 'CONN_ANY', 169: 'CONN_PRG', 170: 'CONN_OUC', 171: 'CONN_R_ID',
21
+ 173: 'PORT', 174: 'RTM', 175: 'PIP',
22
+ 192: 'OB_PCYCLE', 193: 'OB_HWINT', 195: 'OB_DIAG', 196: 'OB_TIMEERROR', 197: 'OB_STARTUP',
23
+ 208: 'DB_ANY', 209: 'DB_WWW', 210: 'DB_DYN'
24
+ };
25
+
26
+ const SUPPORTED_SOFTDATATYPES = new Set([
27
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 19, 20, 22, 23, 24, 28, 29, 40,
28
+ 48, 49, 50, 51, 52, 53, 54, 55, 61, 62, 64, 65, 66, 67, 96,
29
+ 128, 129, 130, 131, 132, 133, 134,
30
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156,
31
+ 160, 161, 162, 163, 164,
32
+ 168, 169, 170, 171, 173, 174, 175,
33
+ 192, 193, 195, 196, 197,
34
+ 208, 209, 210
35
+ ]);
36
+
37
+ function softdatatypeName(id) {
38
+ return SOFTDATATYPE_NAMES[id] || `Softdatatype_${id}`;
39
+ }
40
+
41
+ function isSoftdatatypeSupported(sd) {
42
+ return SUPPORTED_SOFTDATATYPES.has(sd);
43
+ }
44
+
45
+ // System datatypes that carry a type relation (and would otherwise look
46
+ // like a nested struct) but are read/written as a single packed leaf
47
+ // value. Currently only DTL (67). Such types must be classified as leaves
48
+ // in the browse tree, not descended into.
49
+ const PACKED_LEAF_SOFTDATATYPES = new Set([67]);
50
+
51
+ function isPackedLeafDatatype(sd) {
52
+ return PACKED_LEAF_SOFTDATATYPES.has(sd);
53
+ }
54
+
55
+ function getSizeOfDatatype(vte) {
56
+ const oit = vte.offsetInfoType;
57
+ switch (vte.softdatatype) {
58
+ case 1: return 1;
59
+ case 2: case 3: case 40: case 52: case 55: return 1;
60
+ case 4: case 5: case 53: return 2;
61
+ case 6: case 7: case 8: case 10: case 11: case 54: return 4;
62
+ case 9: case 12: case 23: case 24: case 28: case 29: return 2;
63
+ case 14: case 48: case 49: case 50: case 51: case 64: case 65: case 66: return 8;
64
+ case 67: return 12; // DTL: packed 12-byte date/time structure
65
+ case 19: {
66
+ const isArr = (oit.is1Dim && oit.is1Dim()) || (oit.isMDim && oit.isMDim());
67
+ return isArr ? (oit.unspecifiedOffsetinfo1 || 0) + 2 : 0;
68
+ }
69
+ case 20: return 6;
70
+ case 22: return 10;
71
+ // Hardware / system types — 32-bit (AOM_IDENT, EVENT_*, FOLDER, AOM_AID, AOM_LINK, CONN_R_ID)
72
+ case 128: case 129: case 130: case 131: case 132: case 133: case 134: case 171: return 4;
73
+ // Hardware / system types — 16-bit (HW_*, OB_*, CONN_ANY/PRG/OUC, PORT, RTM, PIP, DB_*)
74
+ case 144: case 145: case 146: case 147: case 148: case 149:
75
+ case 150: case 151: case 152: case 153: case 154: case 155: case 156:
76
+ case 160: case 161: case 162: case 163: case 164:
77
+ case 168: case 169: case 170:
78
+ case 173: case 174: case 175:
79
+ case 192: case 193: case 195: case 196: case 197:
80
+ case 208: case 209: case 210: return 2;
81
+ default: return 0;
82
+ }
83
+ }
84
+
85
+ module.exports = {
86
+ eNodeType,
87
+ softdatatypeName,
88
+ isSoftdatatypeSupported,
89
+ isPackedLeafDatatype,
90
+ getSizeOfDatatype
91
+ };
@@ -0,0 +1,433 @@
1
+ 'use strict';
2
+
3
+ const { Ids } = require('../constants');
4
+ const { MEMORY_AREAS } = require('./areas');
5
+ const { eNodeType, softdatatypeName, isSoftdatatypeSupported, isPackedLeafDatatype, getSizeOfDatatype } = require('./datatypes');
6
+ const { applyBoolBitoffsets } = require('./vte-helpers');
7
+ const { computeCrcFromMeta } = require('../crc');
8
+
9
+ function oitLowerBound(oit) {
10
+ if (!oit) return 0;
11
+ if (typeof oit.getArrayLowerBounds === 'function') return oit.getArrayLowerBounds() | 0;
12
+ if (typeof oit.arrayLowerBounds === 'number') return oit.arrayLowerBounds | 0;
13
+ return 0;
14
+ }
15
+
16
+ function crcMetaFromSegments(segments) {
17
+ if (segments.length === 0) return null;
18
+ if (segments.length === 1) return segments[0];
19
+ return { pathSegments: segments };
20
+ }
21
+
22
+ const SOFTDATATYPE_BBOOL = 40;
23
+
24
+ function normalizeMaxSymbols(value) {
25
+ const n = Number(value);
26
+ if (!Number.isFinite(n) || n <= 0) return Infinity;
27
+ return Math.floor(n);
28
+ }
29
+
30
+ function createNode(overrides = {}) {
31
+ return {
32
+ nodeType: eNodeType.Undefined,
33
+ name: '',
34
+ accessId: 0,
35
+ softdatatype: 0,
36
+ relationId: 0,
37
+ vte: null,
38
+ arrayAdrOffsetOpt: 0,
39
+ arrayAdrOffsetNonOpt: 0,
40
+ childs: [],
41
+ ...overrides
42
+ };
43
+ }
44
+
45
+ function findObjectByRelId(objs, relId) {
46
+ if (!objs || relId === 0) return null;
47
+ for (const ob of objs) {
48
+ if (ob.relationId === relId) return ob;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function getTComSize(ob, objs) {
54
+ const attr = ob.getAttribute ? ob.getAttribute(Ids.TI_TComSize) : null;
55
+ if (attr && typeof attr.toJs === 'function') {
56
+ const v = attr.toJs();
57
+ return typeof v === 'number' ? v : Number(v);
58
+ }
59
+ return 0;
60
+ }
61
+
62
+ class FlatBrowser {
63
+ constructor(options = {}) {
64
+ this.rootNodes = [];
65
+ this.typeInfoObjects = [];
66
+ this.varInfoList = [];
67
+ this.maxSymbols = normalizeMaxSymbols(options.maxSymbols);
68
+ this._symbolCount = 0;
69
+ this.limitExceeded = false;
70
+ this._limitPending = false;
71
+ }
72
+
73
+ _noteOversizedArray(elementCount) {
74
+ if (elementCount > this.maxSymbols) {
75
+ this._limitPending = true;
76
+ }
77
+ }
78
+
79
+ _cappedArrayCount(elementCount) {
80
+ if (!Number.isFinite(this.maxSymbols)) return elementCount;
81
+ return Math.min(elementCount, this.maxSymbols);
82
+ }
83
+
84
+ _emitFlatSymbol(info) {
85
+ if (this._symbolCount >= this.maxSymbols) {
86
+ this.limitExceeded = true;
87
+ return false;
88
+ }
89
+ this.varInfoList.push(info);
90
+ this._symbolCount++;
91
+ return true;
92
+ }
93
+
94
+ setTypeInfoContainerObjects(objs) {
95
+ this.typeInfoObjects = objs || [];
96
+ }
97
+
98
+ addBlockNode(nodeType, name, accessId, tiRelId) {
99
+ this.rootNodes.push(createNode({
100
+ nodeType: nodeType || eNodeType.Root,
101
+ name,
102
+ accessId,
103
+ relationId: tiRelId
104
+ }));
105
+ }
106
+
107
+ buildTree() {
108
+ for (let i = 0; i < this.rootNodes.length; i++) {
109
+ const root = this.rootNodes[i];
110
+ const ob = findObjectByRelId(this.typeInfoObjects, root.relationId);
111
+ if (ob) {
112
+ this.addSubNodes(root, ob);
113
+ }
114
+ }
115
+ }
116
+
117
+ buildFlatList() {
118
+ this.varInfoList = [];
119
+ this._symbolCount = 0;
120
+ for (const node of this.rootNodes) {
121
+ if (this.limitExceeded) break;
122
+ if (node.childs.length > 0) {
123
+ this.addFlatSubnodes(node, '', '', 0, 0, []);
124
+ }
125
+ }
126
+ if (this._limitPending || this.limitExceeded) {
127
+ this.limitExceeded = true;
128
+ }
129
+ return this.varInfoList;
130
+ }
131
+
132
+ addFlatSubnodes(node, names, accessIds, optOffset, nonOptOffset, crcPath) {
133
+ if (this.limitExceeded) return;
134
+ let nextCrcPath = crcPath;
135
+
136
+ switch (node.nodeType) {
137
+ case eNodeType.Root:
138
+ names += node.name;
139
+ accessIds += node.accessId.toString(16).toUpperCase();
140
+ break;
141
+ case eNodeType.Array: {
142
+ names += node.name;
143
+ accessIds += '.' + node.accessId.toString(16).toUpperCase();
144
+ const oit = node.vte ? node.vte.offsetInfoType : null;
145
+ const prev = crcPath[crcPath.length - 1];
146
+ nextCrcPath = crcPath.slice(0, -1);
147
+ nextCrcPath.push({
148
+ memberName: prev ? prev.memberName : '',
149
+ softdatatype: prev ? prev.softdatatype : node.softdatatype,
150
+ isArray: true,
151
+ elementSoftdatatype: node.softdatatype,
152
+ lowerBound: oitLowerBound(oit)
153
+ });
154
+ break;
155
+ }
156
+ case eNodeType.StructArray: {
157
+ names += node.name;
158
+ accessIds += '.' + node.accessId.toString(16).toUpperCase() + '.1';
159
+ const oit = node.vte ? node.vte.offsetInfoType : null;
160
+ const prev = crcPath[crcPath.length - 1];
161
+ nextCrcPath = crcPath.slice(0, -1);
162
+ nextCrcPath.push({
163
+ memberName: prev ? prev.memberName : '',
164
+ softdatatype: prev ? prev.softdatatype : node.softdatatype,
165
+ isArray: true,
166
+ elementSoftdatatype: node.softdatatype,
167
+ lowerBound: oitLowerBound(oit)
168
+ });
169
+ break;
170
+ }
171
+ default:
172
+ names += '.' + node.name;
173
+ accessIds += '.' + node.accessId.toString(16).toUpperCase();
174
+ nextCrcPath = crcPath.concat([{
175
+ memberName: node.name,
176
+ softdatatype: node.softdatatype,
177
+ isArray: false,
178
+ elementSoftdatatype: 0,
179
+ lowerBound: 0
180
+ }]);
181
+ break;
182
+ }
183
+
184
+ if (node.childs.length === 0) {
185
+ if (!isSoftdatatypeSupported(node.softdatatype)) return;
186
+ const info = {
187
+ name: names,
188
+ accessSequence: accessIds,
189
+ softdatatype: node.softdatatype,
190
+ softdatatypeName: softdatatypeName(node.softdatatype),
191
+ optAddress: 0,
192
+ nonOptAddress: 0,
193
+ optBitoffset: 0,
194
+ nonOptBitoffset: 0
195
+ };
196
+ if (node.vte) info.symbolCrc = node.vte.symbolCrc;
197
+ const meta = crcMetaFromSegments(nextCrcPath);
198
+ if (meta) info.computedCrc = computeCrcFromMeta(meta);
199
+ if (node.nodeType === eNodeType.Array) {
200
+ info.optAddress = optOffset;
201
+ info.nonOptAddress = nonOptOffset;
202
+ } else if (node.vte && node.vte.offsetInfoType) {
203
+ const oit = node.vte.offsetInfoType;
204
+ info.optAddress = optOffset + (oit.optimizedAddress || 0);
205
+ info.nonOptAddress = nonOptOffset + (oit.nonoptimizedAddress || 0);
206
+ }
207
+ if (node.vte) applyBoolBitoffsets(info, node.vte);
208
+ this._emitFlatSymbol(info);
209
+ return;
210
+ }
211
+
212
+ let nextOpt = optOffset;
213
+ let nextNon = nonOptOffset;
214
+ if (node.vte) {
215
+ const oit = node.vte.offsetInfoType;
216
+ if (node.nodeType === eNodeType.Array && oit) {
217
+ nextOpt = oit.optimizedAddress || 0;
218
+ nextNon = oit.nonoptimizedAddress || 0;
219
+ } else if (node.nodeType === eNodeType.StructArray) {
220
+ nextOpt += node.arrayAdrOffsetOpt;
221
+ nextNon += node.arrayAdrOffsetNonOpt;
222
+ } else if (oit) {
223
+ nextOpt += oit.optimizedAddress || 0;
224
+ nextNon += oit.nonoptimizedAddress || 0;
225
+ }
226
+ }
227
+
228
+ for (const sub of node.childs) {
229
+ if (this.limitExceeded) return;
230
+ if (sub.nodeType === eNodeType.Array) {
231
+ this.addFlatSubnodes(
232
+ sub, names, accessIds,
233
+ nextOpt + sub.arrayAdrOffsetOpt,
234
+ nextNon + sub.arrayAdrOffsetNonOpt,
235
+ nextCrcPath
236
+ );
237
+ } else {
238
+ this.addFlatSubnodes(sub, names, accessIds, nextOpt, nextNon, nextCrcPath);
239
+ }
240
+ }
241
+ }
242
+
243
+ addSubNodes(node, o) {
244
+ if (!o.vartypeList || !o.varnameList) return;
245
+ let elementIndex = 0;
246
+ for (const vte of o.vartypeList.elements) {
247
+ const subnode = createNode({
248
+ name: o.varnameList.names[elementIndex],
249
+ softdatatype: vte.softdatatype,
250
+ accessId: vte.lid,
251
+ vte
252
+ });
253
+ node.childs.push(subnode);
254
+ const oit = vte.offsetInfoType;
255
+
256
+ if (oit && oit.is1Dim && oit.is1Dim()) {
257
+ const count = oit.getArrayElementCount();
258
+ this._noteOversizedArray(count);
259
+ const expandCount = this._cappedArrayCount(count);
260
+ const lower = oit.getArrayLowerBounds();
261
+ for (let i = 0; i < expandCount; i++) {
262
+ if (oit.hasRelation && oit.hasRelation()) {
263
+ const relId = oit.getRelationId();
264
+ const arraynode = createNode({
265
+ nodeType: eNodeType.StructArray,
266
+ name: `[${i + lower}]`,
267
+ softdatatype: vte.softdatatype,
268
+ accessId: i,
269
+ vte
270
+ });
271
+ subnode.childs.push(arraynode);
272
+ const relOb = findObjectByRelId(this.typeInfoObjects, relId);
273
+ if (relOb) {
274
+ const tcom = getTComSize(relOb, this.typeInfoObjects);
275
+ arraynode.arrayAdrOffsetOpt = i * tcom;
276
+ arraynode.arrayAdrOffsetNonOpt = i * tcom;
277
+ this.addSubNodes(arraynode, relOb);
278
+ }
279
+ } else {
280
+ const tcom = getSizeOfDatatype(vte);
281
+ subnode.childs.push(createNode({
282
+ nodeType: eNodeType.Array,
283
+ name: `[${i + lower}]`,
284
+ softdatatype: vte.softdatatype,
285
+ accessId: i,
286
+ vte,
287
+ arrayAdrOffsetOpt: i * tcom,
288
+ arrayAdrOffsetNonOpt: i * tcom
289
+ }));
290
+ }
291
+ }
292
+ } else if (oit && oit.isMDim && oit.isMDim()) {
293
+ this.addMdimArrayChildren(subnode, vte, oit);
294
+ } else if (oit && oit.hasRelation && oit.hasRelation() && !isPackedLeafDatatype(vte.softdatatype)) {
295
+ const relOb = findObjectByRelId(this.typeInfoObjects, oit.getRelationId());
296
+ if (relOb) this.addSubNodes(subnode, relOb);
297
+ }
298
+ // A packed leaf system type (e.g. DTL) keeps no children and is
299
+ // emitted as a single leaf symbol by buildFlatList.
300
+ elementIndex++;
301
+ }
302
+ }
303
+
304
+ addMdimArrayChildren(subnode, vte, oit) {
305
+ const arrayElementCount = oit.getArrayElementCount();
306
+ this._noteOversizedArray(arrayElementCount);
307
+ const cappedCount = this._cappedArrayCount(arrayElementCount);
308
+ const mdimCounts = oit.getMdimArrayElementCount();
309
+ const mdimLowers = oit.getMdimArrayLowerBounds();
310
+ let actdimensions = 0;
311
+ for (let d = 0; d < 6; d++) {
312
+ if (mdimCounts[d] > 0) actdimensions++;
313
+ }
314
+ const xx = [0, 0, 0, 0, 0, 0];
315
+ let n = 1;
316
+ let id = 0;
317
+ do {
318
+ let aname = '[';
319
+ for (let j = actdimensions - 1; j >= 0; j--) {
320
+ aname += (xx[j] + mdimLowers[j]).toString();
321
+ if (j > 0) aname += ',';
322
+ else aname += ']';
323
+ }
324
+ if (oit.hasRelation()) {
325
+ const relId = oit.getRelationId();
326
+ const arraynode = createNode({
327
+ nodeType: eNodeType.StructArray,
328
+ name: aname,
329
+ softdatatype: vte.softdatatype,
330
+ accessId: id,
331
+ vte
332
+ });
333
+ subnode.childs.push(arraynode);
334
+ const relOb = findObjectByRelId(this.typeInfoObjects, relId);
335
+ if (relOb) {
336
+ const tcom = getTComSize(relOb, this.typeInfoObjects);
337
+ arraynode.arrayAdrOffsetOpt = (n - 1) * tcom;
338
+ arraynode.arrayAdrOffsetNonOpt = (n - 1) * tcom;
339
+ this.addSubNodes(arraynode, relOb);
340
+ }
341
+ } else {
342
+ const tcom = getSizeOfDatatype(vte);
343
+ subnode.childs.push(createNode({
344
+ nodeType: eNodeType.Array,
345
+ name: aname,
346
+ softdatatype: vte.softdatatype,
347
+ accessId: id,
348
+ vte,
349
+ arrayAdrOffsetOpt: (n - 1) * tcom,
350
+ arrayAdrOffsetNonOpt: (n - 1) * tcom
351
+ }));
352
+ }
353
+ xx[0]++;
354
+ if (subnode.softdatatype === SOFTDATATYPE_BBOOL && xx[0] >= mdimCounts[0]) {
355
+ if (mdimCounts[0] % 8 !== 0) {
356
+ id += 8 - (xx[0] % 8);
357
+ }
358
+ }
359
+ for (let dim = 0; dim < 5; dim++) {
360
+ if (xx[dim] >= mdimCounts[dim]) {
361
+ xx[dim] = 0;
362
+ xx[dim + 1]++;
363
+ }
364
+ }
365
+ id++;
366
+ n++;
367
+ } while (n <= cappedCount);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Enumerate the type relation ids a single type object references through
373
+ * its members. Mirrors the exact relation conditions FlatBrowser.addSubNodes
374
+ * uses to descend into nested types, so a transitive fetch of these ids
375
+ * yields every type object findObjectByRelId will later look up:
376
+ * - 1-dim array member with a relation -> element type relId
377
+ * - m-dim array member with a relation -> element type relId
378
+ * - scalar member with a relation that is NOT a packed leaf (DTL) -> relId
379
+ * @param {object} typeOb - a type object carrying a vartypeList
380
+ * @returns {number[]} referenced relation ids (may contain duplicates/zeros)
381
+ */
382
+ function collectReferencedRelIds(typeOb) {
383
+ const ids = [];
384
+ if (!typeOb || !typeOb.vartypeList || !typeOb.vartypeList.elements) return ids;
385
+ for (const vte of typeOb.vartypeList.elements) {
386
+ const oit = vte.offsetInfoType;
387
+ if (!oit) continue;
388
+ if (oit.is1Dim && oit.is1Dim()) {
389
+ if (oit.hasRelation && oit.hasRelation()) ids.push(oit.getRelationId());
390
+ } else if (oit.isMDim && oit.isMDim()) {
391
+ if (oit.hasRelation && oit.hasRelation()) ids.push(oit.getRelationId());
392
+ } else if (oit.hasRelation && oit.hasRelation() && !isPackedLeafDatatype(vte.softdatatype)) {
393
+ ids.push(oit.getRelationId());
394
+ }
395
+ }
396
+ return ids;
397
+ }
398
+
399
+ /**
400
+ * Build flat symbol list from DB roots + type-info container objects (C# Browse).
401
+ * @param {Array} dbList - from _fetchDbListFromPlc
402
+ * @param {object[]} typeInfoObjects - children of OMSTypeInfoContainer
403
+ * @param {object} [options]
404
+ * @param {number} [options.maxSymbols]
405
+ * @returns {{ symbols: object[], limitExceeded: boolean, maxSymbols: number|null }}
406
+ */
407
+ function buildFlatSymbolList(dbList, typeInfoObjects, options = {}) {
408
+ const browser = new FlatBrowser(options);
409
+ browser.setTypeInfoContainerObjects(typeInfoObjects);
410
+ const scope = options.scope;
411
+ const scoped = scope && scope.everything === false;
412
+
413
+ for (const ed of dbList) {
414
+ browser.addBlockNode(eNodeType.Root, ed.db_name, ed.db_block_relid, ed.db_block_ti_relid);
415
+ }
416
+ for (const area of MEMORY_AREAS) {
417
+ if (scoped && !scope.areas.includes(area.name)) continue;
418
+ browser.addBlockNode(eNodeType.Root, area.name, area.accessId, area.tiRelId);
419
+ }
420
+ browser.buildTree();
421
+ return {
422
+ symbols: browser.buildFlatList(),
423
+ limitExceeded: browser.limitExceeded,
424
+ maxSymbols: browser.maxSymbols === Infinity ? null : browser.maxSymbols
425
+ };
426
+ }
427
+
428
+ module.exports = {
429
+ FlatBrowser,
430
+ buildFlatSymbolList,
431
+ findObjectByRelId,
432
+ collectReferencedRelIds
433
+ };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const { encodeNodeId, decodeNodeId } = require('./node-id');
4
+ const { eNodeType, softdatatypeName, isSoftdatatypeSupported, getSizeOfDatatype } = require('./datatypes');
5
+ const { ARRAY_PAGE_SIZE, listBlockRoots, listChildren, resolveLeaf, resolveTypeName } = require('./lazy');
6
+ const { resolveSymbolicPath, parseSymbolSegments } = require('./resolve-symbolic');
7
+
8
+ module.exports = {
9
+ encodeNodeId,
10
+ decodeNodeId,
11
+ eNodeType,
12
+ softdatatypeName,
13
+ isSoftdatatypeSupported,
14
+ getSizeOfDatatype,
15
+ ARRAY_PAGE_SIZE,
16
+ listBlockRoots,
17
+ listChildren,
18
+ resolveLeaf,
19
+ resolveTypeName,
20
+ resolveSymbolicPath,
21
+ parseSymbolSegments
22
+ };