@aryacode/dsh-engineering 0.0.0-stage → 0.4.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/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/cordis.patch.yml +123 -0
- package/lib/client.js +3448 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +94 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- package/vendor/mwi/lib/verify.js +29 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { controllerElementOf } from "../model/parse.js";
|
|
2
|
+
import { elementChildren, firstChild } from "../model/xml.js";
|
|
3
|
+
const integerWidths = { SINT: 8, INT: 16, DINT: 32, LINT: 64 };
|
|
4
|
+
const builtins = {
|
|
5
|
+
TIMER: { EN: 'BOOL', TT: 'BOOL', DN: 'BOOL', PRE: 'DINT', ACC: 'DINT' },
|
|
6
|
+
TIMER_T: { EN: 'BOOL', TT: 'BOOL', DN: 'BOOL', PRE: 'TIME', ACC: 'TIME' },
|
|
7
|
+
COUNTER: { CU: 'BOOL', CD: 'BOOL', DN: 'BOOL', OV: 'BOOL', UN: 'BOOL', PRE: 'DINT', ACC: 'DINT' },
|
|
8
|
+
STRING: { LEN: 'DINT', DATA: 'SINT' },
|
|
9
|
+
};
|
|
10
|
+
const declarations = new WeakMap();
|
|
11
|
+
const key = (scope, name) => `${scope.toLowerCase()}::${name.toLowerCase()}`;
|
|
12
|
+
const dimensionsOf = (element) => {
|
|
13
|
+
const value = element.attrs.Dimensions ?? element.attrs.Dimension ?? '';
|
|
14
|
+
return value === '' || value === '0' ? [] : value.split(/[,\s]+/u).map(Number);
|
|
15
|
+
};
|
|
16
|
+
function tableOf(document) {
|
|
17
|
+
const cached = declarations.get(document);
|
|
18
|
+
if (cached !== undefined)
|
|
19
|
+
return cached;
|
|
20
|
+
const table = new Map();
|
|
21
|
+
const add = (container, scope, family, child) => {
|
|
22
|
+
const entries = container === undefined ? undefined : firstChild(container, family);
|
|
23
|
+
if (entries === undefined)
|
|
24
|
+
return;
|
|
25
|
+
for (const element of elementChildren(entries, child)) {
|
|
26
|
+
const name = element.attrs.Name ?? '';
|
|
27
|
+
table.set(key(scope, name), { name, scope, type: element.attrs.DataType ?? '', dimensions: dimensionsOf(element), ...(element.attrs.AliasFor === undefined ? {} : { alias: element.attrs.AliasFor }) });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const controller = controllerElementOf(document);
|
|
31
|
+
add(controller, 'controller', 'Tags', 'Tag');
|
|
32
|
+
const programs = firstChild(controller, 'Programs');
|
|
33
|
+
if (programs !== undefined)
|
|
34
|
+
for (const program of elementChildren(programs, 'Program'))
|
|
35
|
+
add(program, program.attrs.Name ?? '', 'Tags', 'Tag');
|
|
36
|
+
const aois = firstChild(controller, 'AddOnInstructionDefinitions');
|
|
37
|
+
if (aois !== undefined)
|
|
38
|
+
for (const aoi of elementChildren(aois, 'AddOnInstructionDefinition')) {
|
|
39
|
+
const scope = `AOI::${aoi.attrs.Name ?? ''}`;
|
|
40
|
+
add(aoi, scope, 'Parameters', 'Parameter');
|
|
41
|
+
add(aoi, scope, 'LocalTags', 'LocalTag');
|
|
42
|
+
}
|
|
43
|
+
declarations.set(document, table);
|
|
44
|
+
return table;
|
|
45
|
+
}
|
|
46
|
+
function segmentsOf(symbol) {
|
|
47
|
+
const parts = [];
|
|
48
|
+
let start = 0;
|
|
49
|
+
let depth = 0;
|
|
50
|
+
for (let cursor = 0; cursor < symbol.length; cursor++) {
|
|
51
|
+
if (symbol[cursor] === '[')
|
|
52
|
+
depth++;
|
|
53
|
+
if (symbol[cursor] === ']')
|
|
54
|
+
depth--;
|
|
55
|
+
if (depth < 0)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (symbol[cursor] === '.' && depth === 0) {
|
|
58
|
+
parts.push(symbol.slice(start, cursor));
|
|
59
|
+
start = cursor + 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (depth !== 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
parts.push(symbol.slice(start));
|
|
65
|
+
const result = [];
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
const matched = /^([A-Za-z_][A-Za-z0-9_:]*|\d+)(?:\[([^\[\]]+)\])?$/u.exec(part);
|
|
68
|
+
if (matched === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
result.push({ name: matched[1], ...(matched[2] === undefined ? {} : { indices: matched[2].split(',').map(value => value.trim()) }) });
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/** Resolve an address without discarding indices or assuming an absent member exists.
|
|
75
|
+
* @param document - Export containing tag, AOI and UDT declarations.
|
|
76
|
+
* @param scope - Program name, controller, or AOI::name.
|
|
77
|
+
* @param symbol - Address, optionally qualified as Program:name.Tag.
|
|
78
|
+
* @returns Resolution with canonical declaration spelling, final type, alias chain and unresolved reason.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveTag(document, scope, symbol) {
|
|
81
|
+
const table = tableOf(document);
|
|
82
|
+
const aliases = [];
|
|
83
|
+
let indicesVerified = true;
|
|
84
|
+
const fail = (reason) => ({ symbol, scope, resolved: false, aliases, indices_verified: indicesVerified, reason });
|
|
85
|
+
let localScope = scope;
|
|
86
|
+
let address = symbol;
|
|
87
|
+
const qualified = /^Program:([A-Za-z_][A-Za-z0-9_]*)\.(.+)$/iu.exec(address);
|
|
88
|
+
if (qualified !== null) {
|
|
89
|
+
localScope = qualified[1];
|
|
90
|
+
address = qualified[2];
|
|
91
|
+
}
|
|
92
|
+
let segments = segmentsOf(address);
|
|
93
|
+
if (segments === undefined || segments.length === 0)
|
|
94
|
+
return fail('Malformed address');
|
|
95
|
+
let declaration = table.get(key(localScope, segments[0].name));
|
|
96
|
+
if (declaration === undefined && !localScope.startsWith('AOI::') && qualified === null)
|
|
97
|
+
declaration = table.get(key('controller', segments[0].name));
|
|
98
|
+
if (declaration === undefined)
|
|
99
|
+
return fail('Declaration is absent from the export');
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
while (declaration.alias !== undefined) {
|
|
102
|
+
const identity = key(declaration.scope, declaration.name);
|
|
103
|
+
if (seen.has(identity))
|
|
104
|
+
return fail('Cyclic alias');
|
|
105
|
+
seen.add(identity);
|
|
106
|
+
aliases.push(`${declaration.scope}::${declaration.name}`);
|
|
107
|
+
const targetQualification = /^Program:([A-Za-z_][A-Za-z0-9_]*)\.(.+)$/iu.exec(declaration.alias);
|
|
108
|
+
const targetScope = targetQualification?.[1] ?? declaration.scope;
|
|
109
|
+
const target = segmentsOf(targetQualification?.[2] ?? declaration.alias);
|
|
110
|
+
if (target === undefined)
|
|
111
|
+
return fail('Malformed alias target');
|
|
112
|
+
if (segments[0].indices !== undefined)
|
|
113
|
+
return fail('Subscript on an alias requires explicit target verification');
|
|
114
|
+
segments = [...target, ...segments.slice(1)];
|
|
115
|
+
declaration = table.get(key(targetScope, target[0].name)) ?? (targetQualification === null ? table.get(key('controller', target[0].name)) : undefined);
|
|
116
|
+
if (declaration === undefined)
|
|
117
|
+
return fail('Alias target is absent from the export');
|
|
118
|
+
}
|
|
119
|
+
let dataType = declaration.type;
|
|
120
|
+
let dimensions = declaration.dimensions;
|
|
121
|
+
const canonical = [];
|
|
122
|
+
for (const [position, segment] of segments.entries()) {
|
|
123
|
+
let name = declaration.name;
|
|
124
|
+
if (position > 0) {
|
|
125
|
+
const previousType = dataType.toUpperCase();
|
|
126
|
+
if (/^\d+$/u.test(segment.name)) {
|
|
127
|
+
if (position !== segments.length - 1 || segment.indices !== undefined || Number(segment.name) >= (integerWidths[previousType] ?? 0))
|
|
128
|
+
return fail('Invalid integer bit address');
|
|
129
|
+
name = segment.name;
|
|
130
|
+
dataType = 'BOOL';
|
|
131
|
+
dimensions = [];
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const builtin = builtins[previousType]?.[segment.name.toUpperCase()];
|
|
135
|
+
const udt = document.udts.find(entry => entry.name.toLowerCase() === dataType.toLowerCase());
|
|
136
|
+
const member = udt?.members.find(entry => entry.name.toLowerCase() === segment.name.toLowerCase());
|
|
137
|
+
const aoi = document.aois.find(entry => entry.name.toLowerCase() === dataType.toLowerCase());
|
|
138
|
+
const parameter = aoi?.parameters.find(entry => entry.name.toLowerCase() === segment.name.toLowerCase());
|
|
139
|
+
if (builtin === undefined && member === undefined && parameter === undefined)
|
|
140
|
+
return fail(`Member ${segment.name} is absent from ${dataType}`);
|
|
141
|
+
name = member?.name ?? parameter?.name ?? segment.name.toUpperCase();
|
|
142
|
+
dataType = builtin ?? member?.dataType ?? parameter.dataType;
|
|
143
|
+
dimensions = member?.dimension === undefined ? [] : [member.dimension];
|
|
144
|
+
if (previousType === 'STRING' && name === 'DATA')
|
|
145
|
+
dimensions = [82];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (segment.indices !== undefined) {
|
|
149
|
+
if (segment.indices.length !== dimensions.length)
|
|
150
|
+
return fail('Array rank does not match its declaration');
|
|
151
|
+
for (const [index, value] of segment.indices.entries()) {
|
|
152
|
+
if (/^\d+$/u.test(value)) {
|
|
153
|
+
if (Number(value) >= dimensions[index])
|
|
154
|
+
return fail('Array subscript is outside its declared bounds');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (/^-\d+$/u.test(value))
|
|
158
|
+
return fail('Array subscript is negative');
|
|
159
|
+
indicesVerified = false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
name += `[${segment.indices.join(',')}]`;
|
|
163
|
+
}
|
|
164
|
+
else if (dimensions.length > 0 && position !== segments.length - 1)
|
|
165
|
+
return fail('Array member requires a subscript');
|
|
166
|
+
canonical.push(name);
|
|
167
|
+
}
|
|
168
|
+
return { symbol, scope, resolved: true, canonical: `${declaration.scope}::${canonical.join('.')}`, data_type: dataType, aliases, indices_verified: indicesVerified };
|
|
169
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indexed and bit-level usage of one array tag.
|
|
3
|
+
*
|
|
4
|
+
* Reviewing alarm words, HMI index maps and timer tables means asking which
|
|
5
|
+
* elements and bits a program already uses. Parsing references answers that from
|
|
6
|
+
* the same evidence the reference tools report; it does not prove runtime
|
|
7
|
+
* consumption by an external HMI or another controller.
|
|
8
|
+
*/
|
|
9
|
+
import { type ProjectSnapshot } from '../session.ts';
|
|
10
|
+
/** Map one declared tag's indexed and bit-level occurrences.
|
|
11
|
+
* @param project - Opened export.
|
|
12
|
+
* @param scope - Declaring scope: `controller` or a program name.
|
|
13
|
+
* @param name - Declared tag name.
|
|
14
|
+
* @returns Used and free element bits, read/write counts and examples.
|
|
15
|
+
*/
|
|
16
|
+
export declare function usageMap(project: ProjectSnapshot, scope: string, name: string): object;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indexed and bit-level usage of one array tag.
|
|
3
|
+
*
|
|
4
|
+
* Reviewing alarm words, HMI index maps and timer tables means asking which
|
|
5
|
+
* elements and bits a program already uses. Parsing references answers that from
|
|
6
|
+
* the same evidence the reference tools report; it does not prove runtime
|
|
7
|
+
* consumption by an external HMI or another controller.
|
|
8
|
+
*/
|
|
9
|
+
import { evidenceOf } from "./analysis.js";
|
|
10
|
+
import { EngineeringError } from "../session.js";
|
|
11
|
+
/** Bits an element exposes when the declared type has a fixed width. */
|
|
12
|
+
const ELEMENT_BITS = { DINT: 32, INT: 16, SINT: 8 };
|
|
13
|
+
function rowOf(rows, index) {
|
|
14
|
+
const existing = rows.get(index);
|
|
15
|
+
if (existing !== undefined)
|
|
16
|
+
return existing;
|
|
17
|
+
const created = { index, bits: new Set(), reads: 0, writes: 0, unknown: 0, dynamic: false, examples: [] };
|
|
18
|
+
rows.set(index, created);
|
|
19
|
+
return created;
|
|
20
|
+
}
|
|
21
|
+
function count(row, access) {
|
|
22
|
+
if (access === 'read')
|
|
23
|
+
row.reads += 1;
|
|
24
|
+
else if (access === 'write')
|
|
25
|
+
row.writes += 1;
|
|
26
|
+
else
|
|
27
|
+
row.unknown += 1;
|
|
28
|
+
}
|
|
29
|
+
/** Map one declared tag's indexed and bit-level occurrences.
|
|
30
|
+
* @param project - Opened export.
|
|
31
|
+
* @param scope - Declaring scope: `controller` or a program name.
|
|
32
|
+
* @param name - Declared tag name.
|
|
33
|
+
* @returns Used and free element bits, read/write counts and examples.
|
|
34
|
+
*/
|
|
35
|
+
export function usageMap(project, scope, name) {
|
|
36
|
+
const tag = project.document.tags.find(entry => entry.name.toLowerCase() === name.toLowerCase() && entry.scope.toLowerCase() === scope.toLowerCase());
|
|
37
|
+
if (tag === undefined)
|
|
38
|
+
throw new EngineeringError('COMPONENT_NOT_FOUND', `Tag ${name} is not declared in scope ${scope}`);
|
|
39
|
+
const rows = new Map();
|
|
40
|
+
const scopePrefix = `${tag.scope}::`.toLowerCase();
|
|
41
|
+
for (const reference of evidenceOf(project)) {
|
|
42
|
+
// The raw operand keeps array indices and bit digits that the canonical
|
|
43
|
+
// resolver drops; the resolved scope proves it names this declaration.
|
|
44
|
+
if (reference.resolved !== undefined && !reference.resolved.toLowerCase().startsWith(scopePrefix))
|
|
45
|
+
continue;
|
|
46
|
+
const raw = reference.symbol.toLowerCase();
|
|
47
|
+
if (!raw.startsWith(tag.name.toLowerCase()))
|
|
48
|
+
continue;
|
|
49
|
+
const suffix = reference.symbol.slice(tag.name.length);
|
|
50
|
+
// `Alarm` must not claim `Alarms[0]`; the next character settles it.
|
|
51
|
+
if (suffix !== '' && !/^[.[]/u.test(suffix))
|
|
52
|
+
continue;
|
|
53
|
+
// A member path after an index (`Alarms[0].6`) is a bit; a member after the
|
|
54
|
+
// tag itself (`Cooling.Blower_1`) is structure access, not an array index.
|
|
55
|
+
const indexes = [...suffix.matchAll(/\[([^\]]*)\]/gu)].map(match => match[1] ?? '');
|
|
56
|
+
const bits = [...suffix.replace(/\[[^\]]*\]/gu, '').matchAll(/\.(\d+)/gu)].map(match => Number.parseInt(match[1] ?? '', 10));
|
|
57
|
+
const wholeElement = indexes.length === 0;
|
|
58
|
+
const dynamic = indexes.some(value => !/^\d+$/u.test(value));
|
|
59
|
+
const index = dynamic || indexes.length === 0 ? null : Number.parseInt(indexes[0], 10);
|
|
60
|
+
const row = rowOf(rows, wholeElement ? null : index);
|
|
61
|
+
if (dynamic) {
|
|
62
|
+
row.dynamic = true;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
for (const bit of bits)
|
|
66
|
+
if (Number.isInteger(bit))
|
|
67
|
+
row.bits.add(bit);
|
|
68
|
+
}
|
|
69
|
+
count(row, reference.access);
|
|
70
|
+
if (row.examples.length < 3)
|
|
71
|
+
row.examples.push(`${reference.routine}${reference.rung === undefined ? '' : ` rung ${reference.rung}`}${reference.line === undefined ? '' : ` line ${reference.line}`}: ${reference.symbol}`);
|
|
72
|
+
}
|
|
73
|
+
const width = ELEMENT_BITS[tag.dataType.toUpperCase()];
|
|
74
|
+
const indexes = [...rows.values()].filter(row => row.index !== null).sort((left, right) => left.index - right.index);
|
|
75
|
+
const whole = rows.get(null);
|
|
76
|
+
return {
|
|
77
|
+
project_id: project.id, source_sha256: project.sha256,
|
|
78
|
+
tag: { name: tag.name, scope: tag.scope, data_type: tag.dataType },
|
|
79
|
+
element_bit_width: width ?? null,
|
|
80
|
+
indexes: indexes.map(row => ({
|
|
81
|
+
index: row.index,
|
|
82
|
+
bits_used: [...row.bits].sort((left, right) => left - right),
|
|
83
|
+
bits_free: width === undefined ? null : Array.from({ length: width }, (_value, bit) => bit).filter(bit => !row.bits.has(bit)),
|
|
84
|
+
dynamic_index: row.dynamic,
|
|
85
|
+
reads: row.reads, writes: row.writes, unknown: row.unknown,
|
|
86
|
+
examples: row.examples,
|
|
87
|
+
})),
|
|
88
|
+
whole_element: whole === undefined ? null : { reads: whole.reads, writes: whole.writes, unknown: whole.unknown, bits_used: [...whole.bits].sort((left, right) => left - right), examples: whole.examples },
|
|
89
|
+
complete: false,
|
|
90
|
+
note: 'Counts cover exported RLL/ST/FBD operands only. External HMI, produced/consumed and driver consumers are not visible; dynamic indices are not expanded.',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Element positions in the original export, including its start and closing tags. */
|
|
2
|
+
export interface XmlRegion {
|
|
3
|
+
readonly kind: string;
|
|
4
|
+
readonly attributes: Readonly<Record<string, string>>;
|
|
5
|
+
readonly start: number;
|
|
6
|
+
readonly contentStart: number;
|
|
7
|
+
readonly contentEnd: number;
|
|
8
|
+
readonly end: number;
|
|
9
|
+
readonly children: readonly XmlRegion[];
|
|
10
|
+
readonly selfClosing: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Locate elements in XML that has already passed the L5X parser.
|
|
13
|
+
* @param xml - Validated export text.
|
|
14
|
+
* @returns Source regions; malformed or unsupported markup throws before editing.
|
|
15
|
+
*/
|
|
16
|
+
export declare function xmlRegions(xml: string): readonly XmlRegion[];
|
|
17
|
+
/** Find exactly one direct child, with optional case-insensitive Logix name matching.
|
|
18
|
+
* @param parents - Source regions to search.
|
|
19
|
+
* @param kind - XML element name.
|
|
20
|
+
* @param name - Optional Name attribute.
|
|
21
|
+
* @returns Unique matching region, or throws for absent or ambiguous targets.
|
|
22
|
+
*/
|
|
23
|
+
export declare function uniqueRegion(parents: readonly XmlRegion[], kind: string, name?: string): XmlRegion;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Locate validated XML elements without serializing untouched source bytes. */
|
|
2
|
+
import { documentRoot, parseXml } from "../model/xml.js";
|
|
3
|
+
import { EngineeringError } from "../session.js";
|
|
4
|
+
/** Locate elements in XML that has already passed the L5X parser.
|
|
5
|
+
* @param xml - Validated export text.
|
|
6
|
+
* @returns Source regions; malformed or unsupported markup throws before editing.
|
|
7
|
+
*/
|
|
8
|
+
export function xmlRegions(xml) {
|
|
9
|
+
const roots = [];
|
|
10
|
+
const stack = [];
|
|
11
|
+
let cursor = 0;
|
|
12
|
+
while (cursor < xml.length) {
|
|
13
|
+
const start = xml.indexOf('<', cursor);
|
|
14
|
+
if (start < 0)
|
|
15
|
+
break;
|
|
16
|
+
const skip = xml.startsWith('<!--', start) ? '-->' : xml.startsWith('<![CDATA[', start) ? ']]>' : xml.startsWith('<?', start) ? '?>' : undefined;
|
|
17
|
+
if (skip !== undefined) {
|
|
18
|
+
const end = xml.indexOf(skip, start + 2);
|
|
19
|
+
if (end < 0)
|
|
20
|
+
throw new EngineeringError('INVALID_XML_REGION', 'Unterminated XML text block');
|
|
21
|
+
cursor = end + skip.length;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (xml.startsWith('<!', start))
|
|
25
|
+
throw new EngineeringError('UNSUPPORTED_XML', 'XML declarations other than comments and CDATA cannot be edited');
|
|
26
|
+
let quote = '';
|
|
27
|
+
let end = start + 1;
|
|
28
|
+
for (; end < xml.length; end++) {
|
|
29
|
+
const char = xml[end];
|
|
30
|
+
if (quote !== '') {
|
|
31
|
+
if (char === quote)
|
|
32
|
+
quote = '';
|
|
33
|
+
}
|
|
34
|
+
else if (char === '"' || char === "'")
|
|
35
|
+
quote = char;
|
|
36
|
+
else if (char === '>')
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (end === xml.length)
|
|
40
|
+
throw new EngineeringError('INVALID_XML_REGION', 'Unterminated XML element');
|
|
41
|
+
cursor = end + 1;
|
|
42
|
+
const markup = xml.slice(start, cursor);
|
|
43
|
+
if (markup.startsWith('</')) {
|
|
44
|
+
const region = stack.pop();
|
|
45
|
+
if (region === undefined || markup.slice(2, -1).trim() !== region.kind)
|
|
46
|
+
throw new EngineeringError('INVALID_XML_REGION', 'Mismatched closing element');
|
|
47
|
+
region.contentEnd = start;
|
|
48
|
+
region.end = cursor;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const selfClosing = /\/\s*>$/u.test(markup);
|
|
52
|
+
const element = documentRoot(parseXml(selfClosing ? markup : markup.slice(0, -1) + '/>'));
|
|
53
|
+
const region = { kind: element.name, attributes: element.attrs, start, contentStart: cursor, contentEnd: cursor, end: cursor, children: [], selfClosing };
|
|
54
|
+
const parent = stack[stack.length - 1];
|
|
55
|
+
if (parent === undefined)
|
|
56
|
+
roots.push(region);
|
|
57
|
+
else
|
|
58
|
+
parent.children.push(region);
|
|
59
|
+
if (!selfClosing)
|
|
60
|
+
stack.push(region);
|
|
61
|
+
}
|
|
62
|
+
if (stack.length > 0)
|
|
63
|
+
throw new EngineeringError('INVALID_XML_REGION', 'Unclosed XML element');
|
|
64
|
+
return roots;
|
|
65
|
+
}
|
|
66
|
+
/** Find exactly one direct child, with optional case-insensitive Logix name matching.
|
|
67
|
+
* @param parents - Source regions to search.
|
|
68
|
+
* @param kind - XML element name.
|
|
69
|
+
* @param name - Optional Name attribute.
|
|
70
|
+
* @returns Unique matching region, or throws for absent or ambiguous targets.
|
|
71
|
+
*/
|
|
72
|
+
export function uniqueRegion(parents, kind, name) {
|
|
73
|
+
const matches = parents.filter(region => region.kind === kind && (name === undefined || region.attributes.Name?.toLowerCase() === name.toLowerCase()));
|
|
74
|
+
if (matches.length !== 1)
|
|
75
|
+
throw new EngineeringError('AMBIGUOUS_TARGET', `Expected one ${kind}${name === undefined ? '' : ` named ${name}`}; found ${matches.length}`);
|
|
76
|
+
return matches[0];
|
|
77
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte-to-text decoding for one Studio 5000 export.
|
|
3
|
+
*
|
|
4
|
+
* An export's encoding is a property of the machine that wrote it: Studio
|
|
5
|
+
* writes UTF-16 with a byte-order mark for some generated files and UTF-8 for
|
|
6
|
+
* others. The byte-order mark wins; the XML declaration is consulted only when
|
|
7
|
+
* there is none.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x/fs
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Decode one export's bytes.
|
|
13
|
+
*
|
|
14
|
+
* @param bytes - The file contents.
|
|
15
|
+
* @returns The decoded XML text.
|
|
16
|
+
*/
|
|
17
|
+
export declare function decodeExport(bytes: Uint8Array): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte-to-text decoding for one Studio 5000 export.
|
|
3
|
+
*
|
|
4
|
+
* An export's encoding is a property of the machine that wrote it: Studio
|
|
5
|
+
* writes UTF-16 with a byte-order mark for some generated files and UTF-8 for
|
|
6
|
+
* others. The byte-order mark wins; the XML declaration is consulted only when
|
|
7
|
+
* there is none.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x/fs
|
|
10
|
+
*/
|
|
11
|
+
import { L5xError } from "./core/errors.js";
|
|
12
|
+
/**
|
|
13
|
+
* Decode one export's bytes.
|
|
14
|
+
*
|
|
15
|
+
* @param bytes - The file contents.
|
|
16
|
+
* @returns The decoded XML text.
|
|
17
|
+
*/
|
|
18
|
+
export function decodeExport(bytes) {
|
|
19
|
+
if (bytes.length >= 2 && bytes[0] === 0xFF && bytes[1] === 0xFE)
|
|
20
|
+
return new TextDecoder('utf-16le').decode(bytes);
|
|
21
|
+
if (bytes.length >= 2 && bytes[0] === 0xFE && bytes[1] === 0xFF)
|
|
22
|
+
return new TextDecoder('utf-16be').decode(bytes);
|
|
23
|
+
if (bytes.length >= 3 && bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF)
|
|
24
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
25
|
+
const head = new TextDecoder('latin1').decode(bytes.subarray(0, 256));
|
|
26
|
+
const declared = /^<\?xml[^>]*?encoding\s*=\s*["']([^"']+)["']/u.exec(head)?.[1];
|
|
27
|
+
if (declared !== undefined && declared.toLowerCase() !== 'utf-8') {
|
|
28
|
+
try {
|
|
29
|
+
return new TextDecoder(declared).decode(bytes);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new L5xError('UNSUPPORTED_ENCODING', `The export declares encoding ${JSON.stringify(declared)}, which this host cannot decode.`, 'Re-export the project as UTF-8 from Studio 5000 and reopen it.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rebuilt offline L5X read surface.
|
|
3
|
+
*
|
|
4
|
+
* Eleven read tools, one intent each, with described parameters, a
|
|
5
|
+
* summary-first result and repairable failures. The project handles it opens are
|
|
6
|
+
* provided as the `l5xProjects` service so the editing entry can draft against
|
|
7
|
+
* the same exports without opening them twice.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x
|
|
10
|
+
*/
|
|
11
|
+
import type { PluginContext } from './core/tool.ts';
|
|
12
|
+
/** Plugin name in a composition. */
|
|
13
|
+
export declare const name = "l5x-read";
|
|
14
|
+
/** Services this plugin cannot register without. */
|
|
15
|
+
export declare const inject: readonly ["tools", "fs"];
|
|
16
|
+
/** Service name the editing entry consumes. */
|
|
17
|
+
export declare const PROJECTS_SERVICE = "l5xProjects";
|
|
18
|
+
/** Configuration for the read surface. */
|
|
19
|
+
export interface Config {
|
|
20
|
+
/** Largest export, in bytes, one open will read. Defaults to 64 MiB. */
|
|
21
|
+
maxFileBytes?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Register the offline read surface.
|
|
25
|
+
*
|
|
26
|
+
* @param ctx - Plugin context with the tool registry and filesystem provider.
|
|
27
|
+
* @param config - Deployment limits.
|
|
28
|
+
*/
|
|
29
|
+
export declare function apply(ctx: PluginContext, config?: Config): void;
|
|
30
|
+
export { ProjectSession } from './session.ts';
|
|
31
|
+
export { readTools } from './tools/read.ts';
|
|
32
|
+
export { referenceTools } from './tools/references.ts';
|
|
33
|
+
export { checkTools } from './tools/checks.ts';
|
|
34
|
+
export { L5xError } from './core/errors.ts';
|
|
35
|
+
export type { NextStep, Coverage, Success } from './core/result.ts';
|
|
36
|
+
export type { ToolDefinition } from './core/tool.ts';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rebuilt offline L5X read surface.
|
|
3
|
+
*
|
|
4
|
+
* Eleven read tools, one intent each, with described parameters, a
|
|
5
|
+
* summary-first result and repairable failures. The project handles it opens are
|
|
6
|
+
* provided as the `l5xProjects` service so the editing entry can draft against
|
|
7
|
+
* the same exports without opening them twice.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x
|
|
10
|
+
*/
|
|
11
|
+
import { ProjectSession } from "./session.js";
|
|
12
|
+
import { readTools } from "./tools/read.js";
|
|
13
|
+
import { referenceTools } from "./tools/references.js";
|
|
14
|
+
import { checkTools } from "./tools/checks.js";
|
|
15
|
+
/** Plugin name in a composition. */
|
|
16
|
+
export const name = 'l5x-read';
|
|
17
|
+
/** Services this plugin cannot register without. */
|
|
18
|
+
export const inject = ['tools', 'fs'];
|
|
19
|
+
/** Service name the editing entry consumes. */
|
|
20
|
+
export const PROJECTS_SERVICE = 'l5xProjects';
|
|
21
|
+
/**
|
|
22
|
+
* Register the offline read surface.
|
|
23
|
+
*
|
|
24
|
+
* @param ctx - Plugin context with the tool registry and filesystem provider.
|
|
25
|
+
* @param config - Deployment limits.
|
|
26
|
+
*/
|
|
27
|
+
export function apply(ctx, config = {}) {
|
|
28
|
+
const maxFileBytes = config.maxFileBytes ?? 64 * 1024 * 1024;
|
|
29
|
+
if (!Number.isInteger(maxFileBytes) || maxFileBytes <= 0) {
|
|
30
|
+
throw new Error('l5x-read: maxFileBytes must be a positive integer');
|
|
31
|
+
}
|
|
32
|
+
const session = new ProjectSession(ctx.fs, maxFileBytes);
|
|
33
|
+
ctx.provide?.(PROJECTS_SERVICE, session);
|
|
34
|
+
for (const definition of [...readTools(session), ...referenceTools(session), ...checkTools(session)]) {
|
|
35
|
+
ctx.tools.register(definition);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export { ProjectSession } from "./session.js";
|
|
39
|
+
export { readTools } from "./tools/read.js";
|
|
40
|
+
export { referenceTools } from "./tools/references.js";
|
|
41
|
+
export { checkTools } from "./tools/checks.js";
|
|
42
|
+
export { L5xError } from "./core/errors.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The L5X live entry: SDK-free EtherNet/IP reads and trends.
|
|
3
|
+
*
|
|
4
|
+
* Reads use the package's polling worker and its own TCP connection, so no
|
|
5
|
+
* Studio 5000, FactoryTalk Linx service or SDK is required. The entry is off by
|
|
6
|
+
* default; enabling the row is the switch that permits controller traffic.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-l5x/live
|
|
9
|
+
*/
|
|
10
|
+
import type { PluginContext } from './core/tool.ts';
|
|
11
|
+
/** Plugin name in a composition. */
|
|
12
|
+
export declare const name = "l5x-live";
|
|
13
|
+
/** The read entry's project service must already be mounted. */
|
|
14
|
+
export declare const inject: readonly ["tools", "l5xProjects"];
|
|
15
|
+
/** Configuration for the live entry. */
|
|
16
|
+
export interface Config {
|
|
17
|
+
/** Permit controller traffic. Defaults to true; the entry row is the switch. */
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
/** Minimum requested sweep interval. Defaults to 50 ms. */
|
|
20
|
+
minIntervalMs?: number;
|
|
21
|
+
/** Default requested sweep interval. Defaults to 200 ms. */
|
|
22
|
+
defaultIntervalMs?: number;
|
|
23
|
+
/** Most scalar tags in one window. Defaults to 16. */
|
|
24
|
+
maxTags?: number;
|
|
25
|
+
/** Most sweeps in one window. Defaults to 1200. */
|
|
26
|
+
maxSamples?: number;
|
|
27
|
+
/** Longest window, in milliseconds. Defaults to 60000. */
|
|
28
|
+
maxDurationMs?: number;
|
|
29
|
+
/** Per-read driver timeout. Defaults to 3000 ms. */
|
|
30
|
+
requestTimeoutMs?: number;
|
|
31
|
+
/** Most simultaneous controller windows across chats. Defaults to 2. */
|
|
32
|
+
maxConcurrent?: number;
|
|
33
|
+
/** Optional IPv4 allowlist; empty permits any explicit address. */
|
|
34
|
+
allowedAddresses?: string[];
|
|
35
|
+
/** Chart redraw interval, independent of reads. Defaults to 250 ms. */
|
|
36
|
+
displayRefreshMs?: number;
|
|
37
|
+
/** Most points retained per chart. Defaults to 1200. */
|
|
38
|
+
displayMaxSamples?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Register the live tools.
|
|
42
|
+
*
|
|
43
|
+
* @param ctx - Plugin context carrying the read entry's project service.
|
|
44
|
+
* @param config - Deployment limits.
|
|
45
|
+
*/
|
|
46
|
+
export declare function apply(ctx: PluginContext, config?: Config): void;
|
|
47
|
+
export { DirectPolling } from './engine/direct-polling.ts';
|
|
48
|
+
export { liveTools } from './tools/live.ts';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The L5X live entry: SDK-free EtherNet/IP reads and trends.
|
|
3
|
+
*
|
|
4
|
+
* Reads use the package's polling worker and its own TCP connection, so no
|
|
5
|
+
* Studio 5000, FactoryTalk Linx service or SDK is required. The entry is off by
|
|
6
|
+
* default; enabling the row is the switch that permits controller traffic.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-engineering-l5x/live
|
|
9
|
+
*/
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { DirectPolling } from "./engine/direct-polling.js";
|
|
12
|
+
import { liveTools } from "./tools/live.js";
|
|
13
|
+
import { PROJECTS_SERVICE } from "./index.js";
|
|
14
|
+
/** Plugin name in a composition. */
|
|
15
|
+
export const name = 'l5x-live';
|
|
16
|
+
/** The read entry's project service must already be mounted. */
|
|
17
|
+
export const inject = ['tools', PROJECTS_SERVICE];
|
|
18
|
+
/**
|
|
19
|
+
* Register the live tools.
|
|
20
|
+
*
|
|
21
|
+
* @param ctx - Plugin context carrying the read entry's project service.
|
|
22
|
+
* @param config - Deployment limits.
|
|
23
|
+
*/
|
|
24
|
+
export function apply(ctx, config = {}) {
|
|
25
|
+
const session = ctx.get?.(PROJECTS_SERVICE);
|
|
26
|
+
if (session === undefined) {
|
|
27
|
+
throw new Error('l5x-live: the l5xProjects service is unavailable; mount the L5X read entry before this one');
|
|
28
|
+
}
|
|
29
|
+
const polling = new DirectPolling(fileURLToPath(new URL('../poll-worker.mjs', import.meta.url)), {
|
|
30
|
+
enabled: config.enabled ?? true,
|
|
31
|
+
minIntervalMs: config.minIntervalMs ?? 50,
|
|
32
|
+
defaultIntervalMs: config.defaultIntervalMs ?? 200,
|
|
33
|
+
maxTags: config.maxTags ?? 16,
|
|
34
|
+
maxSamples: config.maxSamples ?? 1200,
|
|
35
|
+
maxDurationMs: config.maxDurationMs ?? 60_000,
|
|
36
|
+
requestTimeoutMs: config.requestTimeoutMs ?? 3000,
|
|
37
|
+
maxConcurrent: config.maxConcurrent ?? 2,
|
|
38
|
+
allowedAddresses: config.allowedAddresses ?? [],
|
|
39
|
+
displayRefreshMs: config.displayRefreshMs ?? 250,
|
|
40
|
+
displayMaxSamples: config.displayMaxSamples ?? 1200,
|
|
41
|
+
});
|
|
42
|
+
const effect = ctx.effect;
|
|
43
|
+
effect?.(() => () => { void polling.dispose(); });
|
|
44
|
+
for (const definition of liveTools(session, polling, ctx))
|
|
45
|
+
ctx.tools.register(definition);
|
|
46
|
+
}
|
|
47
|
+
export { DirectPolling } from "./engine/direct-polling.js";
|
|
48
|
+
export { liveTools } from "./tools/live.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add-on-instruction reader.
|
|
3
|
+
*
|
|
4
|
+
* An AOI carries both its interface — parameters and local tags — and its
|
|
5
|
+
* internal logic, and both are reported together because an instruction is only
|
|
6
|
+
* usable when its pins and its behavior are known.
|
|
7
|
+
*/
|
|
8
|
+
import type { L5xAoiDefinition, L5xAoiInfo, L5xDocument } from './types.ts';
|
|
9
|
+
import { type XmlElement } from './xml.ts';
|
|
10
|
+
/**
|
|
11
|
+
* List the add-on instructions the controller defines.
|
|
12
|
+
*
|
|
13
|
+
* @param document - The parsed project.
|
|
14
|
+
* @returns One entry per instruction, in written order.
|
|
15
|
+
*/
|
|
16
|
+
export declare function listAois(document: L5xDocument): readonly L5xAoiInfo[];
|
|
17
|
+
/**
|
|
18
|
+
* Read every add-on instruction, with its interface and internal logic.
|
|
19
|
+
*
|
|
20
|
+
* @param document - The parsed project.
|
|
21
|
+
* @returns One definition per instruction, in written order.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getAois(document: L5xDocument): readonly L5xAoiDefinition[];
|
|
24
|
+
/**
|
|
25
|
+
* Read one add-on instruction, with its interface and internal logic.
|
|
26
|
+
*
|
|
27
|
+
* @param document - The parsed project.
|
|
28
|
+
* @param name - Instruction name.
|
|
29
|
+
* @returns The definition, or undefined when the controller does not define
|
|
30
|
+
* that instruction.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getAoi(document: L5xDocument, name: string): L5xAoiDefinition | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Read every add-on instruction definition of a controller.
|
|
35
|
+
*
|
|
36
|
+
* @param controller - The controller element.
|
|
37
|
+
* @returns The definitions, in written order.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildAois(controller: XmlElement): readonly L5xAoiDefinition[];
|