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,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
|
+
};
|