@codesys/mcp-toolkit 2.4.0 → 2.6.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 +34 -0
- package/README.md +15 -9
- package/README.zh-CN.md +11 -6
- package/dist/codesys/device-tools.d.ts +21 -0
- package/dist/codesys/device-tools.js +60 -0
- package/dist/codesys/device-tools.js.map +1 -1
- package/dist/codesys/devices.d.ts +6 -0
- package/dist/codesys/devices.js +24 -1
- package/dist/codesys/devices.js.map +1 -1
- package/dist/codesys/graphical-read.d.ts +63 -0
- package/dist/codesys/graphical-read.js +214 -0
- package/dist/codesys/graphical-read.js.map +1 -0
- package/dist/codesys/graphical.js +13 -1
- package/dist/codesys/graphical.js.map +1 -1
- package/dist/hmi/native.d.ts +14 -2
- package/dist/hmi/native.js +97 -5
- package/dist/hmi/native.js.map +1 -1
- package/dist/hmi/tools.js +25 -16
- package/dist/hmi/tools.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -2
- package/dist/tools.js +20 -5
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/watcher/mcp_devices.py +158 -0
- package/watcher/mcp_visu.py +81 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.6.0] - 2026-11-14
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `device_parameters` and `configure_device`: read and set a device's
|
|
14
|
+
parameters (IP addresses, ports, unit IDs, enumerations such as DHCP) and map
|
|
15
|
+
its I/O channels to variables, with automatic or fixed IEC addresses, through
|
|
16
|
+
CODESYS's documented device scripting. Checked on SP22: a Modbus TCP server
|
|
17
|
+
device's register and a single bit mapped, 0 build errors.
|
|
18
|
+
|
|
19
|
+
- Native gauges look like the browser HMI's arc gauge (pictures per 2.5 %
|
|
20
|
+
of the range, in the band colours) instead of a bar.
|
|
21
|
+
- Native alarm rows follow CODESYS's alarm manager: the time the alarm came,
|
|
22
|
+
its state, and an ACK button that acknowledges it in CODESYS; a cleared alarm
|
|
23
|
+
stays until acknowledged. Checked live on SP21.
|
|
24
|
+
- `show_in_codesys`: with `--show-window`, open an object (a visualization, a
|
|
25
|
+
POU, a device) in its editor in the CODESYS window.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- `get_io_list`, `get_devices` and `project_report` listed a mapped channel
|
|
29
|
+
once for every device above it; each now appears once, under its own device.
|
|
30
|
+
|
|
31
|
+
## [2.5.0] - 2026-11-14
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- Ladder, FBD and SFC POUs read back as `create_graphical`'s description:
|
|
35
|
+
`read_objects` turns CODESYS's PLCopenXML into rungs (contacts in series
|
|
36
|
+
and parallel, coils, set/reset), networks (functions, function blocks with
|
|
37
|
+
their inputs and outputs, outputs written on the block) or steps (actions,
|
|
38
|
+
conditions, the jump back). To change one, Claude edits the description and
|
|
39
|
+
calls `create_graphical` with `replace: true`. What the description can't
|
|
40
|
+
hold (function boxes in a rung, SFC branches) is named, with the XML.
|
|
41
|
+
Checked against CODESYS SP22's own export: read, add a rung, replace,
|
|
42
|
+
compile with 0 errors, read back.
|
|
43
|
+
|
|
10
44
|
## [2.4.0] - 2026-11-14
|
|
11
45
|
|
|
12
46
|
### Added
|
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ operation. [The reference](docs/reference.md) describes each one.
|
|
|
117
117
|
| **Build** | `compile` |
|
|
118
118
|
| **Libraries** | `list_libraries` `add_library` `remove_library` `get_library_api` `get_signature` |
|
|
119
119
|
| **Navigate code** | `find_references` `rename_symbol` |
|
|
120
|
-
| **Tasks and I/O** | `list_tasks` `configure_task` `get_io_list` `get_devices` `find_device` `add_device` |
|
|
120
|
+
| **Tasks and I/O** | `list_tasks` `configure_task` `get_io_list` `get_devices` `find_device` `add_device` `device_parameters` `configure_device` |
|
|
121
121
|
| **Change tracking** | `checkpoint` `get_changes` `revert_object` |
|
|
122
122
|
| **Simulation** | `codesys_sim_start` `codesys_sim_stop` `codesys_sim_status` `codesys_sim_control` `codesys_sim_cycle` `codesys_sim_read` `codesys_sim_write` `codesys_sim_force` `codesys_sim_unforce` `codesys_sim_test` |
|
|
123
123
|
| **Trace** | `trace_setup` `trace_record` |
|
|
@@ -125,7 +125,7 @@ operation. [The reference](docs/reference.md) describes each one.
|
|
|
125
125
|
| **HMI screens** | `hmi_save_screen` `hmi_template` `hmi_screenshot` `hmi_export_codesys` `hmi_list` `hmi_delete_screen` `open_hmi` `hmi_save_symbol` `hmi_symbols` `hmi_delete_symbol` |
|
|
126
126
|
| **Alarms** | `alarm_list` `alarm_status` `alarm_acknowledge` |
|
|
127
127
|
| **Handover** | `project_report` `export_sources` |
|
|
128
|
-
| **Helpers** | `batch` `wait` `help` `open_workspace` |
|
|
128
|
+
| **Helpers** | `batch` `wait` `help` `open_workspace` `show_in_codesys` |
|
|
129
129
|
|
|
130
130
|
`batch` runs up to 50 actions in one call, which is much faster than separate
|
|
131
131
|
calls.
|
|
@@ -141,8 +141,10 @@ feature isn't available, or not work.
|
|
|
141
141
|
|
|
142
142
|
Claude writes Structured Text, and creates ladder (LD), FBD and SFC programs
|
|
143
143
|
and function blocks with `create_graphical` from a simple description of rungs,
|
|
144
|
-
networks or steps; CODESYS draws them.
|
|
145
|
-
|
|
144
|
+
networks or steps; CODESYS draws them. Reading an LD, FBD or SFC POU gives the
|
|
145
|
+
same description, so Claude can change existing graphical code too: it edits the
|
|
146
|
+
description and sends it back with `replace: true`. CFC isn't supported, and
|
|
147
|
+
stays read-only as PLCopenXML.
|
|
146
148
|
|
|
147
149
|
## Live panel
|
|
148
150
|
|
|
@@ -256,11 +258,15 @@ and the symbol's on and fault variables choose which one CODESYS shows.
|
|
|
256
258
|
|
|
257
259
|

|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
Gauges are the same arc gauge as in the browser (pictures in the image pool,
|
|
262
|
+
one per 2.5 % of the range, in the band's colour), not CODESYS's chrome meter.
|
|
263
|
+
The alarm rows follow CODESYS's alarm manager: a row stays until the alarm is
|
|
264
|
+
acknowledged, with the time it came, its state and an ACK button that
|
|
265
|
+
acknowledges it in CODESYS, instead of CODESYS's grey alarm table. Trends show
|
|
266
|
+
their current values: a CODESYS trend needs a trend recording that scripting
|
|
267
|
+
can't create yet. Checked on CODESYS V3.5 SP21 in the simulation.
|
|
268
|
+
|
|
269
|
+
 A visualization needs the `FloatingPointUtils` library in CODESYS's
|
|
264
270
|
library repository; if it's missing, the export says so before it builds
|
|
265
271
|
anything. A long export reports its progress (each page, the alarms, the
|
|
266
272
|
compile) to clients that show MCP progress.
|
package/README.zh-CN.md
CHANGED
|
@@ -205,8 +205,13 @@ Claude 可以在仿真中测试这些报警:`alarm_status` 从 CODESYS 报警
|
|
|
205
205
|
|
|
206
206
|

|
|
207
207
|
|
|
208
|
-
CODESYS
|
|
209
|
-
|
|
208
|
+
仪表与浏览器中的弧形仪表相同(图片池中的图片,每 2.5% 一张,按色带着色),而不是 CODESYS 的金属仪表。
|
|
209
|
+
报警行跟随 CODESYS 报警管理器:报警被确认之前一直显示,带有报警出现的时间、状态和 ACK 按钮(在 CODESYS
|
|
210
|
+
中确认报警),而不是 CODESYS 的灰色报警表。趋势显示当前值:CODESYS 趋势需要脚本目前无法创建的趋势记录。
|
|
211
|
+
|
|
212
|
+

|
|
213
|
+
|
|
214
|
+
已在 CODESYS
|
|
210
215
|
V3.5 SP21 的仿真中验证。可视化需要 CODESYS 库存储库中有 `FloatingPointUtils` 库;如果缺少,导出会在
|
|
211
216
|
开始构建之前提示。
|
|
212
217
|
|
|
@@ -236,7 +241,7 @@ V3.5 SP21 的仿真中验证。可视化需要 CODESYS 库存储库中有 `Float
|
|
|
236
241
|
| **编译** | `compile` |
|
|
237
242
|
| **库** | `list_libraries` `add_library` `remove_library` `get_library_api` `get_signature` |
|
|
238
243
|
| **代码导航** | `find_references` `rename_symbol` |
|
|
239
|
-
| **任务与 I/O** | `list_tasks` `configure_task` `get_io_list` `get_devices` `find_device` `add_device` |
|
|
244
|
+
| **任务与 I/O** | `list_tasks` `configure_task` `get_io_list` `get_devices` `find_device` `add_device` `device_parameters` `configure_device` |
|
|
240
245
|
| **改动跟踪** | `checkpoint` `get_changes` `revert_object` |
|
|
241
246
|
| **仿真** | `codesys_sim_start` `codesys_sim_stop` `codesys_sim_status` `codesys_sim_control` `codesys_sim_cycle` `codesys_sim_read` `codesys_sim_write` `codesys_sim_force` `codesys_sim_unforce` `codesys_sim_test` |
|
|
242
247
|
| **跟踪(Trace)** | `trace_setup` `trace_record` |
|
|
@@ -244,7 +249,7 @@ V3.5 SP21 的仿真中验证。可视化需要 CODESYS 库存储库中有 `Float
|
|
|
244
249
|
| **报警** | `alarm_list` `alarm_status` `alarm_acknowledge` |
|
|
245
250
|
| **交接** | `project_report` `export_sources` |
|
|
246
251
|
| **HMI 画面** | `hmi_save_screen` `hmi_template` `hmi_screenshot` `hmi_export_codesys` `hmi_list` `hmi_delete_screen` `open_hmi` `hmi_save_symbol` `hmi_symbols` `hmi_delete_symbol` |
|
|
247
|
-
| **辅助** | `batch` `wait` `help` `open_workspace` |
|
|
252
|
+
| **辅助** | `batch` `wait` `help` `open_workspace` `show_in_codesys` |
|
|
248
253
|
|
|
249
254
|
`batch` 可以在一次调用中执行最多 50 个操作,比逐个调用快得多。
|
|
250
255
|
|
|
@@ -254,8 +259,8 @@ V3.5 SP21 的仿真中验证。可视化需要 CODESYS 库存储库中有 `Float
|
|
|
254
259
|
验证(见[更新日志](CHANGELOG.md)),在其他版本上可能提示功能不可用,或无法正常工作。
|
|
255
260
|
|
|
256
261
|
Claude 编写结构化文本,并可用 `create_graphical` 根据梯级、网络或步骤的简单描述创建梯形图(LD)、
|
|
257
|
-
FBD 和 SFC 程序及功能块,由 CODESYS
|
|
258
|
-
|
|
262
|
+
FBD 和 SFC 程序及功能块,由 CODESYS 绘制。读取 LD、FBD 或 SFC POU 时会得到同样的描述,因此 Claude
|
|
263
|
+
也能修改已有的图形化代码:修改描述后用 `replace: true` 发回即可。不支持 CFC,CFC 仍以 PLCopenXML 只读显示。
|
|
259
264
|
|
|
260
265
|
## 安全
|
|
261
266
|
|
|
@@ -1,3 +1,24 @@
|
|
|
1
1
|
import type { ActionResult, RegisterAction } from "../actions.js";
|
|
2
2
|
import type { AppContext } from "../tools.js";
|
|
3
|
+
export interface DeviceParam {
|
|
4
|
+
key: string;
|
|
5
|
+
name: string;
|
|
6
|
+
depth: number;
|
|
7
|
+
description?: string;
|
|
8
|
+
unit?: string;
|
|
9
|
+
section?: string;
|
|
10
|
+
type?: string;
|
|
11
|
+
access?: string;
|
|
12
|
+
channel?: string;
|
|
13
|
+
value?: string;
|
|
14
|
+
default?: string;
|
|
15
|
+
choices?: string[];
|
|
16
|
+
mapping?: {
|
|
17
|
+
variable: string;
|
|
18
|
+
address: string;
|
|
19
|
+
automatic_address: boolean;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** One parameter or channel as a line: key, name, value (and choices), mapping. */
|
|
23
|
+
export declare function describeParam(p: DeviceParam): string;
|
|
3
24
|
export declare function registerDeviceActions(reg: RegisterAction, ctx: AppContext, run: (fn: () => Promise<string>) => Promise<ActionResult>): void;
|
|
@@ -4,8 +4,68 @@
|
|
|
4
4
|
* (watcher/mcp_devices.py). Offline: nothing goes online.
|
|
5
5
|
*/
|
|
6
6
|
import * as z from "zod/v4";
|
|
7
|
+
/** One parameter or channel as a line: key, name, value (and choices), mapping. */
|
|
8
|
+
export function describeParam(p) {
|
|
9
|
+
const pad = " ".repeat(p.depth);
|
|
10
|
+
const val = p.value !== undefined ? ` = ${p.value}${p.unit ? ` ${p.unit}` : ""}` : "";
|
|
11
|
+
const choices = p.choices?.length ? ` (one of ${p.choices.slice(0, 12).join(", ")}${p.choices.length > 12 ? ", ..." : ""})` : "";
|
|
12
|
+
const map = p.mapping ? ` -> ${p.mapping.variable || "(not mapped)"}${p.mapping.address ? ` at ${p.mapping.address}${p.mapping.automatic_address ? "" : " (fixed)"}` : ""}` : "";
|
|
13
|
+
const ch = p.channel ? ` [${p.channel.toLowerCase()} channel${p.type ? ` ${p.type}` : ""}]` : p.type && p.depth === 0 ? ` [${p.type}]` : "";
|
|
14
|
+
const ro = p.access && /^read$|readonly|none/i.test(p.access) ? " (read-only)" : "";
|
|
15
|
+
return `${pad}${p.key} ${p.name}${ch}${val}${choices}${map}${ro}`;
|
|
16
|
+
}
|
|
7
17
|
export function registerDeviceActions(reg, ctx, run) {
|
|
8
18
|
const b = ctx.bridge;
|
|
19
|
+
reg("device_parameters", {
|
|
20
|
+
description: "A device's parameters and I/O channels from its device description (the device and its connectors' parameters, nested elements indented): key, name, value with unit and allowed choices, and for each I/O channel the variable it is mapped to. Use the keys with configure_device. Offline; nothing goes online.",
|
|
21
|
+
inputSchema: z.object({
|
|
22
|
+
device: z.string().describe("Device path from get_devices, e.g. '/PLCWinNT/Ethernet/Modbus_TCP_Server'"),
|
|
23
|
+
filter: z.string().optional().describe("Only parameters whose key, name or section contains this text"),
|
|
24
|
+
channels: z.boolean().optional().describe("Only I/O channels"),
|
|
25
|
+
depth: z.number().int().min(0).max(8).optional().describe("Nesting shown: 0 = parameters only, 1 (default) = also their elements (e.g. each register), 2 = also bits"),
|
|
26
|
+
}),
|
|
27
|
+
}, async ({ device, filter, channels, depth }) => run(async () => {
|
|
28
|
+
const r = await b.call("devices.params", { path: device, limit: 3000 }, { timeoutMs: 120_000 });
|
|
29
|
+
let ps = r.parameters;
|
|
30
|
+
if (channels)
|
|
31
|
+
ps = ps.filter((p) => p.mapping);
|
|
32
|
+
const max = depth ?? (filter ? 8 : 1);
|
|
33
|
+
const hidden = ps.filter((p) => p.depth > max).length;
|
|
34
|
+
ps = ps.filter((p) => p.depth <= max);
|
|
35
|
+
if (filter) {
|
|
36
|
+
const f = filter.toLowerCase();
|
|
37
|
+
ps = ps.filter((p) => `${p.key} ${p.name} ${p.section ?? ""}`.toLowerCase().includes(f));
|
|
38
|
+
}
|
|
39
|
+
if (!ps.length)
|
|
40
|
+
return `${r.path}: no ${channels ? "I/O channels" : "parameters"}${filter ? ` matching '${filter}'` : ""}.`;
|
|
41
|
+
const nChan = ps.filter((p) => p.mapping).length;
|
|
42
|
+
return [
|
|
43
|
+
`${r.path}: ${ps.length} parameter element(s)${nChan ? `, ${nChan} I/O channel(s)` : ""}${r.truncated ? " (the first 3000)" : ""}:`,
|
|
44
|
+
...ps.map(describeParam),
|
|
45
|
+
...(hidden ? [`(${hidden} nested element(s) deeper than ${max} not shown: pass depth or filter)`] : []),
|
|
46
|
+
].join("\n");
|
|
47
|
+
}));
|
|
48
|
+
reg("configure_device", {
|
|
49
|
+
description: "Set a device's parameters and map its I/O channels to variables (keys from device_parameters). values: {key: value} (an enumeration takes its identifier or name). mappings: [{channel: key, variable, address?}]: variable is an existing variable as 'Application.GVL_IO.xValve', or a plain name to let CODESYS create one; '' unmaps; address fixes the IEC address (e.g. '%IX10.0'), null makes it automatic. Offline; compile afterwards.",
|
|
50
|
+
inputSchema: z.object({
|
|
51
|
+
device: z.string(),
|
|
52
|
+
values: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
53
|
+
mappings: z.array(z.object({ channel: z.string(), variable: z.string(), address: z.string().nullable().optional() })).optional(),
|
|
54
|
+
}),
|
|
55
|
+
}, async ({ device, values, mappings }) => run(async () => {
|
|
56
|
+
const r = await b.call("devices.configure", {
|
|
57
|
+
path: device,
|
|
58
|
+
values: Object.entries(values ?? {}).map(([key, value]) => ({ key, value: String(value) })),
|
|
59
|
+
mappings: (mappings ?? []).map((m) => ({ key: m.channel, variable: m.variable, ...(m.address !== undefined ? { address: m.address } : {}) })),
|
|
60
|
+
save: ctx.autosave,
|
|
61
|
+
}, { timeoutMs: 120_000 });
|
|
62
|
+
return [
|
|
63
|
+
`${r.path}: ${r.done.length} change(s)${r.errors.length ? `, ${r.errors.length} failed` : ""}.`,
|
|
64
|
+
...r.done.map((d) => (d.variable !== undefined ? ` ${d.key} -> ${d.variable || "(not mapped)"}${d.address ? ` at ${d.address}` : ""}` : ` ${d.key} = ${d.value}`)),
|
|
65
|
+
...r.errors.map((e) => ` FAILED ${e.key}: ${e.error}`),
|
|
66
|
+
r.done.length ? "Compile to check." : "",
|
|
67
|
+
].filter(Boolean).join("\n");
|
|
68
|
+
}));
|
|
9
69
|
reg("find_device", {
|
|
10
70
|
description: "Search the device descriptions installed in CODESYS (fieldbus masters and slaves such as Modbus TCP, EtherCAT, CANopen, PROFINET; I/O modules; drives) by name. Returns type, id and version to pass to add_device.",
|
|
11
71
|
inputSchema: z.object({ text: z.string().describe("Part of the device name, e.g. 'Modbus TCP', 'EtherCAT Master', 'CANopen'"), limit: z.number().int().min(1).max(100).default(30) }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-tools.js","sourceRoot":"","sources":["../../src/codesys/device-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"device-tools.js","sourceRoot":"","sources":["../../src/codesys/device-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,4 +33,10 @@ export interface IoChannel {
|
|
|
33
33
|
export declare function parseIoMappingCsv(text: string): IoChannel[];
|
|
34
34
|
/** "Application.GVL_IO.xValve" -> "GVL_IO.xValve" (the toolkit's paths are relative to the application). */
|
|
35
35
|
export declare const shortVariable: (v: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* The channels mapped to variables across the device tree, each once: a device's
|
|
38
|
+
* export of its I/O mapping also contains its children's, so the same channel
|
|
39
|
+
* shows up under every device above it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mappedChannels(devices: DeviceInfo[]): IoChannel[];
|
|
36
42
|
export declare function describeDevices(devices: DeviceInfo[]): string;
|
package/dist/codesys/devices.js
CHANGED
|
@@ -64,6 +64,26 @@ export function parseIoMappingCsv(text) {
|
|
|
64
64
|
}
|
|
65
65
|
/** "Application.GVL_IO.xValve" -> "GVL_IO.xValve" (the toolkit's paths are relative to the application). */
|
|
66
66
|
export const shortVariable = (v) => v.replace(/^[A-Za-z_][A-Za-z0-9_]*\.(?=[A-Za-z_][A-Za-z0-9_]*\.)/, "");
|
|
67
|
+
/**
|
|
68
|
+
* The channels mapped to variables across the device tree, each once: a device's
|
|
69
|
+
* export of its I/O mapping also contains its children's, so the same channel
|
|
70
|
+
* shows up under every device above it.
|
|
71
|
+
*/
|
|
72
|
+
export function mappedChannels(devices) {
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const d of devices)
|
|
76
|
+
for (const c of d.io_csv ? parseIoMappingCsv(d.io_csv) : []) {
|
|
77
|
+
if (!c.variable)
|
|
78
|
+
continue;
|
|
79
|
+
const k = `${c.device}|${c.parameter}|${c.address}|${c.variable}`;
|
|
80
|
+
if (seen.has(k))
|
|
81
|
+
continue;
|
|
82
|
+
seen.add(k);
|
|
83
|
+
out.push(c);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
67
87
|
export function describeDevices(devices) {
|
|
68
88
|
if (!devices.length)
|
|
69
89
|
return "No devices in the project.";
|
|
@@ -74,7 +94,10 @@ export function describeDevices(devices) {
|
|
|
74
94
|
: d.id
|
|
75
95
|
? ` (type ${d.type ?? "?"}, id ${d.id}, version ${d.version ?? "?"})`
|
|
76
96
|
: "";
|
|
77
|
-
|
|
97
|
+
// A device's export also lists its children's channels: keep its own (the export names the device of each).
|
|
98
|
+
const all = d.io_csv ? parseIoMappingCsv(d.io_csv) : [];
|
|
99
|
+
const own = all.filter((c) => !c.device || c.device === d.name);
|
|
100
|
+
const channels = own.length || !all.length ? own : all.filter((c) => !devices.some((o) => o !== d && o.name === c.device));
|
|
78
101
|
const mapped = channels.filter((c) => c.variable).length;
|
|
79
102
|
lines.push(`${" ".repeat(d.depth)}${d.name}${ident}${d.enabled === false ? " [disabled]" : ""}${channels.length ? `: ${channels.length} channel(s), ${mapped} mapped` : ""}${d.io_error ? ` (I/O mapping not readable: ${d.io_error})` : ""}`);
|
|
80
103
|
for (const c of channels) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/codesys/devices.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"devices.js","sourceRoot":"","sources":["../../src/codesys/devices.ts"],"names":[],"mappings":"AAAA,qGAAqG;AA8BrG,mDAAmD;AACnD,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAClC,GAAG,IAAI,GAAG,CAAC;gBACX,CAAC,EAAE,CAAC;YACN,CAAC;;gBAAM,MAAM,GAAG,CAAC,MAAM,CAAC;QAC1B,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACd,GAAG,GAAG,EAAE,CAAC;QACX,CAAC;;YAAM,GAAG,IAAI,CAAC,CAAC;IAClB,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACd,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnE,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IACpE,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5F,MAAM,GAAG,GAAG,CAAC,EAAU,EAAE,QAAgB,EAAE,EAAE;QAC3C,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC/B,CAAC,CAAC;IACF,MAAM,EAAE,GAAG;QACT,QAAQ,EAAE,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACpC,SAAS,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACrB,WAAW,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;QAC3B,MAAM,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;KAC1B,CAAC;IACF,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAS;QAC9C,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC3B,GAAG,CAAC,IAAI,CAAC;YACP,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE;YAC9B,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE;YAChC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;YACtB,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE;YACpC,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE;YAC5B,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE;SAC3B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,uDAAuD,EAAE,EAAE,CAAC,CAAC;AAEnH;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAqB;IAClD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,OAAO;QACrB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC5D,IAAI,CAAC,CAAC,CAAC,QAAQ;gBAAE,SAAS;YAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YAClE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,SAAS;YAC1B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACd,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAqB;IACnD,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,4BAA4B,CAAC;IACzD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM;YACpB,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG;YACrE,CAAC,CAAC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,GAAG,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,OAAO,IAAI,GAAG,GAAG;gBACrE,CAAC,CAAC,EAAE,CAAC;QACT,4GAA4G;QAC5G,MAAM,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3H,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;QACzD,KAAK,CAAC,IAAI,CACR,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,MAAM,gBAAgB,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACpO,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;QACnO,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ladder, FBD and SFC bodies read back as the description create_graphical
|
|
3
|
+
* takes, so the model can understand a graphical POU and edit it (change the
|
|
4
|
+
* description, create it again with replace). Works on the PLCopenXML CODESYS
|
|
5
|
+
* exports (checked against SP22's own export) and on create_graphical's.
|
|
6
|
+
*
|
|
7
|
+
* LD: every coil's power flow is followed back to the rail: contacts in series
|
|
8
|
+
* become {and}, parallel paths {or}. FBD: each output variable (or output
|
|
9
|
+
* written straight on a block) is followed back through blocks to the inputs.
|
|
10
|
+
* SFC: steps, their actions and the transition to the next step. What has no
|
|
11
|
+
* place in the description (function boxes in LD, SFC branches) is reported.
|
|
12
|
+
*/
|
|
13
|
+
import type { Fbd, Logic } from "./graphical.js";
|
|
14
|
+
interface El {
|
|
15
|
+
name: string;
|
|
16
|
+
attrs: Record<string, string>;
|
|
17
|
+
children: El[];
|
|
18
|
+
text: string;
|
|
19
|
+
}
|
|
20
|
+
/** A small XML reader for well-formed PLCopenXML: elements, attributes, text, CDATA. */
|
|
21
|
+
export declare function parseXml(xml: string): El;
|
|
22
|
+
export type GraphicalDescription = {
|
|
23
|
+
kind: "ladder";
|
|
24
|
+
spec: {
|
|
25
|
+
rungs: {
|
|
26
|
+
when: Logic;
|
|
27
|
+
then: {
|
|
28
|
+
coil: string;
|
|
29
|
+
kind: "coil" | "set" | "reset" | "negated";
|
|
30
|
+
}[];
|
|
31
|
+
}[];
|
|
32
|
+
};
|
|
33
|
+
notes: string[];
|
|
34
|
+
} | {
|
|
35
|
+
kind: "fbd";
|
|
36
|
+
spec: {
|
|
37
|
+
networks: {
|
|
38
|
+
expr: Fbd;
|
|
39
|
+
to?: string;
|
|
40
|
+
assign?: Record<string, string>;
|
|
41
|
+
}[];
|
|
42
|
+
};
|
|
43
|
+
notes: string[];
|
|
44
|
+
} | {
|
|
45
|
+
kind: "sfc";
|
|
46
|
+
spec: {
|
|
47
|
+
steps: {
|
|
48
|
+
name: string;
|
|
49
|
+
initial?: boolean;
|
|
50
|
+
actions?: {
|
|
51
|
+
name: string;
|
|
52
|
+
qualifier: string;
|
|
53
|
+
st: string;
|
|
54
|
+
}[];
|
|
55
|
+
next: string;
|
|
56
|
+
when: string;
|
|
57
|
+
}[];
|
|
58
|
+
};
|
|
59
|
+
notes: string[];
|
|
60
|
+
};
|
|
61
|
+
/** A graphical POU's PLCopenXML as create_graphical's description; undefined when the body isn't LD, FBD or SFC. */
|
|
62
|
+
export declare function describeGraphical(xml: string): GraphicalDescription | undefined;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
const ENT = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
|
|
2
|
+
const unesc = (s) => s.replace(/&(lt|gt|amp|quot|apos|#\d+|#x[0-9a-f]+);/gi, (_, e) => (e[0] === "#" ? String.fromCodePoint(e[1] === "x" || e[1] === "X" ? parseInt(e.slice(2), 16) : parseInt(e.slice(1), 10)) : ENT[e.toLowerCase()]));
|
|
3
|
+
/** A small XML reader for well-formed PLCopenXML: elements, attributes, text, CDATA. */
|
|
4
|
+
export function parseXml(xml) {
|
|
5
|
+
const root = { name: "#root", attrs: {}, children: [], text: "" };
|
|
6
|
+
const stack = [root];
|
|
7
|
+
const re = /<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<!\[CDATA\[([\s\S]*?)\]\]>|<!DOCTYPE[^>]*>|<(\/?)([A-Za-z_][\w.:-]*)((?:\s+[\w.:-]+\s*=\s*(?:"[^"]*"|'[^']*'))*)\s*(\/?)>|([^<]+)/g;
|
|
8
|
+
for (const m of xml.matchAll(re)) {
|
|
9
|
+
const top = stack[stack.length - 1];
|
|
10
|
+
if (m[1] !== undefined)
|
|
11
|
+
top.text += m[1];
|
|
12
|
+
else if (m[6] !== undefined)
|
|
13
|
+
top.text += unesc(m[6]);
|
|
14
|
+
else if (m[3]) {
|
|
15
|
+
if (m[2]) {
|
|
16
|
+
if (stack.length > 1)
|
|
17
|
+
stack.pop();
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const attrs = {};
|
|
21
|
+
for (const a of m[4].matchAll(/([\w.:-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g))
|
|
22
|
+
attrs[a[1].replace(/^.*:/, "")] = unesc(a[2] ?? a[3] ?? "");
|
|
23
|
+
const el = { name: m[3].replace(/^.*:/, ""), attrs, children: [], text: "" };
|
|
24
|
+
top.children.push(el);
|
|
25
|
+
if (!m[5])
|
|
26
|
+
stack.push(el);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
31
|
+
const kids = (e, name) => (e?.children ?? []).filter((c) => c.name === name);
|
|
32
|
+
const kid = (e, name) => (e?.children ?? []).find((c) => c.name === name);
|
|
33
|
+
const find = (e, name) => (e.name === name ? e : e.children.reduce((f, c) => f ?? find(c, name), undefined));
|
|
34
|
+
const allText = (e) => (e ? e.text + e.children.map(allText).join("") : "");
|
|
35
|
+
/** The connections of an element's (first) connectionPointIn. */
|
|
36
|
+
const inputsOf = (e) => kids(kid(e, "connectionPointIn"), "connection").map((c) => ({ ref: c.attrs.refLocalId, ...(c.attrs.formalParameter ? { param: c.attrs.formalParameter } : {}) }));
|
|
37
|
+
/** A graphical POU's PLCopenXML as create_graphical's description; undefined when the body isn't LD, FBD or SFC. */
|
|
38
|
+
export function describeGraphical(xml) {
|
|
39
|
+
const doc = parseXml(xml);
|
|
40
|
+
const pou = find(doc, "pou");
|
|
41
|
+
const body = kid(pou, "body") ?? find(doc, "body");
|
|
42
|
+
if (!body)
|
|
43
|
+
return undefined;
|
|
44
|
+
const ld = kid(body, "LD");
|
|
45
|
+
if (ld)
|
|
46
|
+
return readLadder(ld);
|
|
47
|
+
const fbd = kid(body, "FBD");
|
|
48
|
+
if (fbd)
|
|
49
|
+
return readFbd(fbd);
|
|
50
|
+
const sfc = kid(body, "SFC");
|
|
51
|
+
if (sfc)
|
|
52
|
+
return readSfc(sfc, pou);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
function readLadder(ld) {
|
|
56
|
+
const notes = new Set();
|
|
57
|
+
const byId = new Map(ld.children.filter((c) => c.attrs.localId !== undefined).map((c) => [c.attrs.localId, c]));
|
|
58
|
+
const memo = new Map();
|
|
59
|
+
const and = (parts) => {
|
|
60
|
+
const flat = parts.flatMap((p) => (p === null ? [] : typeof p === "object" && "and" in p ? p.and : [p]));
|
|
61
|
+
return flat.length === 0 ? null : flat.length === 1 ? flat[0] : { and: flat };
|
|
62
|
+
};
|
|
63
|
+
/** The condition at an element's output (null = straight from the rail, always TRUE). */
|
|
64
|
+
const flow = (ref, seen = new Set()) => {
|
|
65
|
+
if (memo.has(ref))
|
|
66
|
+
return memo.get(ref);
|
|
67
|
+
const e = byId.get(ref);
|
|
68
|
+
if (!e || seen.has(ref))
|
|
69
|
+
return null;
|
|
70
|
+
seen.add(ref);
|
|
71
|
+
let out;
|
|
72
|
+
if (e.name === "leftPowerRail")
|
|
73
|
+
out = null;
|
|
74
|
+
else if (e.name === "contact" || e.name === "coil") {
|
|
75
|
+
const left = into(e, seen);
|
|
76
|
+
if (e.name === "coil")
|
|
77
|
+
out = left; // power passes through a coil to the next one
|
|
78
|
+
else
|
|
79
|
+
out = and([left, `${e.attrs.negated === "true" ? "NOT " : ""}${allText(kid(e, "variable")).trim()}`]);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
notes.add(`${e.name}${e.attrs.typeName ? ` ${e.attrs.typeName}` : ""} elements (function boxes in a rung) aren't part of the description; read_objects shows the XML`);
|
|
83
|
+
out = into(e, seen);
|
|
84
|
+
}
|
|
85
|
+
memo.set(ref, out);
|
|
86
|
+
return out;
|
|
87
|
+
};
|
|
88
|
+
const into = (e, seen) => {
|
|
89
|
+
const ins = inputsOf(e).map((c) => flow(c.ref, seen));
|
|
90
|
+
if (ins.length <= 1)
|
|
91
|
+
return ins[0] ?? null;
|
|
92
|
+
if (ins.some((x) => x === null))
|
|
93
|
+
return null; // a path straight from the rail: always TRUE
|
|
94
|
+
return { or: ins };
|
|
95
|
+
};
|
|
96
|
+
const rungs = [];
|
|
97
|
+
for (const c of ld.children.filter((x) => x.name === "coil")) {
|
|
98
|
+
const when = into(c, new Set()) ?? "TRUE";
|
|
99
|
+
const kind = c.attrs.storage === "set" ? "set" : c.attrs.storage === "reset" ? "reset" : c.attrs.negated === "true" ? "negated" : "coil";
|
|
100
|
+
const coil = { coil: allText(kid(c, "variable")).trim(), kind };
|
|
101
|
+
const last = rungs[rungs.length - 1];
|
|
102
|
+
if (last && JSON.stringify(last.when) === JSON.stringify(when))
|
|
103
|
+
last.then.push(coil);
|
|
104
|
+
else
|
|
105
|
+
rungs.push({ when, then: [coil] });
|
|
106
|
+
}
|
|
107
|
+
return { kind: "ladder", spec: { rungs }, notes: [...notes] };
|
|
108
|
+
}
|
|
109
|
+
function readFbd(fbd) {
|
|
110
|
+
const notes = new Set();
|
|
111
|
+
const byId = new Map(fbd.children.filter((c) => c.attrs.localId !== undefined).map((c) => [c.attrs.localId, c]));
|
|
112
|
+
const consumed = new Set();
|
|
113
|
+
const build = (ref, param) => {
|
|
114
|
+
const e = byId.get(ref);
|
|
115
|
+
if (!e)
|
|
116
|
+
return "?";
|
|
117
|
+
consumed.add(ref);
|
|
118
|
+
if (e.name === "inVariable" || e.name === "inOutVariable")
|
|
119
|
+
return allText(kid(e, "expression")).trim();
|
|
120
|
+
if (e.name !== "block") {
|
|
121
|
+
notes.add(`${e.name} elements aren't part of the description`);
|
|
122
|
+
return "?";
|
|
123
|
+
}
|
|
124
|
+
const ins = kids(kid(e, "inputVariables"), "variable").map((v) => ({ name: v.attrs.formalParameter, src: inputsOf(v)[0] }));
|
|
125
|
+
const arg = (x) => (x.src ? build(x.src.ref, x.src.param) : "?");
|
|
126
|
+
if (!e.attrs.instanceName)
|
|
127
|
+
return { fn: e.attrs.typeName, args: ins.map(arg) };
|
|
128
|
+
return { fb: e.attrs.typeName, instance: e.attrs.instanceName, inputs: Object.fromEntries(ins.filter((x) => x.src).map((x) => [x.name, arg(x)])), output: param ?? "Q" };
|
|
129
|
+
};
|
|
130
|
+
const nets = [];
|
|
131
|
+
const fbNets = new Map();
|
|
132
|
+
const order = [];
|
|
133
|
+
for (const e of fbd.children) {
|
|
134
|
+
if (e.name === "outVariable" || e.name === "inOutVariable") {
|
|
135
|
+
const src = inputsOf(e)[0];
|
|
136
|
+
if (!src)
|
|
137
|
+
continue;
|
|
138
|
+
const target = allText(kid(e, "expression")).trim();
|
|
139
|
+
const s = byId.get(src.ref);
|
|
140
|
+
if (s?.name === "block" && s.attrs.instanceName) {
|
|
141
|
+
const n = fbNets.get(src.ref) ?? { block: s, outs: {} };
|
|
142
|
+
n.outs[src.param ?? "Q"] = target;
|
|
143
|
+
if (!fbNets.has(src.ref))
|
|
144
|
+
(fbNets.set(src.ref, n), order.push(src.ref));
|
|
145
|
+
}
|
|
146
|
+
else
|
|
147
|
+
order.push({ expr: build(src.ref, src.param), to: target });
|
|
148
|
+
}
|
|
149
|
+
else if (e.name === "block" && e.attrs.instanceName) {
|
|
150
|
+
// Outputs written straight on the block (CODESYS's "output assignment").
|
|
151
|
+
for (const v of kids(kid(e, "outputVariables"), "variable")) {
|
|
152
|
+
const target = allText(kid(kid(v, "connectionPointOut"), "expression")).trim();
|
|
153
|
+
if (!target)
|
|
154
|
+
continue;
|
|
155
|
+
const n = fbNets.get(e.attrs.localId) ?? { block: e, outs: {} };
|
|
156
|
+
n.outs[v.attrs.formalParameter] = target;
|
|
157
|
+
if (!fbNets.has(e.attrs.localId))
|
|
158
|
+
(fbNets.set(e.attrs.localId, n), order.push(e.attrs.localId));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const o of order) {
|
|
163
|
+
if (typeof o !== "string") {
|
|
164
|
+
nets.push(o);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const n = fbNets.get(o);
|
|
168
|
+
const output = "Q" in n.outs ? "Q" : Object.keys(n.outs)[0];
|
|
169
|
+
const { [output]: to, ...assign } = n.outs;
|
|
170
|
+
const expr = build(o, output);
|
|
171
|
+
nets.push({ expr, ...(to ? { to } : {}), ...(Object.keys(assign).length ? { assign } : {}) });
|
|
172
|
+
}
|
|
173
|
+
// Function block calls whose outputs nothing uses.
|
|
174
|
+
for (const e of fbd.children)
|
|
175
|
+
if (e.name === "block" && e.attrs.instanceName && !consumed.has(e.attrs.localId) && !fbNets.has(e.attrs.localId))
|
|
176
|
+
nets.push({ expr: build(e.attrs.localId) });
|
|
177
|
+
return { kind: "fbd", spec: { networks: nets }, notes: [...notes] };
|
|
178
|
+
}
|
|
179
|
+
function readSfc(sfc, pou) {
|
|
180
|
+
const notes = new Set();
|
|
181
|
+
const byId = new Map(sfc.children.filter((c) => c.attrs.localId !== undefined).map((c) => [c.attrs.localId, c]));
|
|
182
|
+
const bodies = new Map(kids(kid(pou, "actions"), "action").map((a) => [a.attrs.name, allText(find(a, "xhtml") ?? find(a, "ST")).trim()]));
|
|
183
|
+
for (const e of sfc.children)
|
|
184
|
+
if (/Divergence|Convergence/.test(e.name))
|
|
185
|
+
notes.add(`${e.name} (a branch): the description holds one sequence; read_objects shows the XML`);
|
|
186
|
+
const after = (ref) => sfc.children.filter((c) => inputsOf(c).some((i) => i.ref === ref));
|
|
187
|
+
const steps = sfc.children
|
|
188
|
+
.filter((c) => c.name === "step")
|
|
189
|
+
.map((s) => {
|
|
190
|
+
const id = s.attrs.localId;
|
|
191
|
+
const acts = after(id)
|
|
192
|
+
.filter((c) => c.name === "actionBlock")
|
|
193
|
+
.flatMap((b) => kids(b, "action").map((a) => {
|
|
194
|
+
const name = kid(a, "reference")?.attrs.name ?? "";
|
|
195
|
+
const inline = allText(find(a, "xhtml")).trim();
|
|
196
|
+
return { name, qualifier: a.attrs.qualifier ?? "N", st: bodies.get(name) ?? inline };
|
|
197
|
+
}));
|
|
198
|
+
const tr = after(id).find((c) => c.name === "transition");
|
|
199
|
+
let when = "";
|
|
200
|
+
let next = "";
|
|
201
|
+
if (tr) {
|
|
202
|
+
const cond = kid(tr, "condition");
|
|
203
|
+
const src = inputsOf(cond)[0];
|
|
204
|
+
when = src ? allText(kid(byId.get(src.ref), "expression")).trim() : allText(find(cond ?? tr, "xhtml")).trim() || (kid(cond, "reference")?.attrs.name ?? "");
|
|
205
|
+
const to = after(tr.attrs.localId)[0];
|
|
206
|
+
next = to?.name === "step" ? to.attrs.name : to?.name === "jumpStep" ? to.attrs.targetName : "";
|
|
207
|
+
if (!to || !next)
|
|
208
|
+
notes.add(`the transition after step ${s.attrs.name} leads into a branch`);
|
|
209
|
+
}
|
|
210
|
+
return { name: s.attrs.name, ...(s.attrs.initialStep === "true" ? { initial: true } : {}), ...(acts.length ? { actions: acts } : {}), next, when };
|
|
211
|
+
});
|
|
212
|
+
return { kind: "sfc", spec: { steps }, notes: [...notes] };
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=graphical-read.js.map
|