@codesys/mcp-toolkit 2.5.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 CHANGED
@@ -7,6 +7,27 @@ 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
+
10
31
  ## [2.5.0] - 2026-11-14
11
32
 
12
33
  ### 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.
@@ -258,11 +258,15 @@ and the symbol's on and fault variables choose which one CODESYS shows.
258
258
 
259
259
  ![The heat treatment line as a native CODESYS visualization in the simulation: the symbols in their on colours, the filter red because it is clogged, and the alarm row](docs/images/hmi-codesys-symbols.png)
260
260
 
261
- CODESYS's scripting can't create trend, meter or alarm-table elements, so
262
- trends show their current values, gauges show a bar, and the alarm page shows a
263
- row per alarm while it is active (acknowledge it in CODESYS's own alarm table,
264
- which you can add from the toolbox). Checked on CODESYS V3.5 SP21 in the
265
- simulation. A visualization needs the `FloatingPointUtils` library in CODESYS's
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
+ ![An exported alarm row in CODESYS: the time it came, "Pump P-101 fault", "cleared - confirm" and an ACK button](docs/images/hmi-codesys-alarm-row.png) A visualization needs the `FloatingPointUtils` library in CODESYS's
266
270
  library repository; if it's missing, the export says so before it builds
267
271
  anything. A long export reports its progress (each page, the alarms, the
268
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
  ![热处理线作为原生 CODESYS 可视化在仿真中运行:符号显示运行颜色,过滤器因堵塞显示为红色,并有报警行](docs/images/hmi-codesys-symbols.png)
207
207
 
208
- CODESYS 的脚本无法创建趋势、仪表或报警表元素,所以趋势显示当前值,仪表显示为棒图,报警页在
209
- 报警活动时显示一行(可在 CODESYS 自带的报警表中确认报警,报警表可从工具箱添加)。已在 CODESYS
208
+ 仪表与浏览器中的弧形仪表相同(图片池中的图片,每 2.5% 一张,按色带着色),而不是 CODESYS 的金属仪表。
209
+ 报警行跟随 CODESYS 报警管理器:报警被确认之前一直显示,带有报警出现的时间、状态和 ACK 按钮(在 CODESYS
210
+ 中确认报警),而不是 CODESYS 的灰色报警表。趋势显示当前值:CODESYS 趋势需要脚本目前无法创建的趋势记录。
211
+
212
+ ![CODESYS 中导出的报警行:报警出现的时间、"Pump P-101 fault"、"cleared - confirm" 和 ACK 按钮](docs/images/hmi-codesys-alarm-row.png)
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
 
@@ -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":"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"}
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;AA8B5B,mFAAmF;AACnF,MAAM,UAAU,aAAa,CAAC,CAAc;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtF,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjI,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,cAAc,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACjL,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5I,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,IAAI,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;IACpF,OAAO,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,EAAE,GAAG,GAAG,GAAG,OAAO,GAAG,GAAG,GAAG,EAAE,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAmB,EAAE,GAAe,EAAE,GAAyD;IACnI,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;IACrB,GAAG,CACD,mBAAmB,EACnB;QACE,WAAW,EACT,oTAAoT;QACtT,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2EAA2E,CAAC;YACxG,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+DAA+D,CAAC;YACvG,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2GAA2G,CAAC;SACvK,CAAC;KACH,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAC5C,GAAG,CAAC,KAAK,IAAI,EAAE;QACb,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAkE,gBAAgB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QACjK,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC;QACtB,IAAI,QAAQ;YAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC;QACtD,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC;QACtC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;YAC/B,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3F,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC,CAAC,IAAI,QAAQ,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;QAC5H,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QACjD,OAAO;YACL,GAAG,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,MAAM,wBAAwB,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,iBAAiB,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,GAAG;YACnI,GAAG,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC;YACxB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,kCAAkC,GAAG,mCAAmC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACxG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CACL,CAAC;IACF,GAAG,CACD,kBAAkB,EAClB;QACE,WAAW,EACT,ibAAib;QACnb,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;YAClB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;YACvF,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;SACjI,CAAC;KACH,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,CACrC,GAAG,CAAC,KAAK,IAAI,EAAE;QACb,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CACpB,mBAAmB,EACnB;YACE,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3F,QAAQ,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAC7I,IAAI,EAAE,GAAG,CAAC,QAAQ;SACnB,EACD,EAAE,SAAS,EAAE,OAAO,EAAE,CACvB,CAAC;QACF,OAAO;YACL,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,aAAa,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG;YAC/F,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ,IAAI,cAAc,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YACpK,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;YACvD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE;SACzC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC,CACL,CAAC;IACF,GAAG,CACD,aAAa,EACb;QACE,WAAW,EAAE,qNAAqN;QAClO,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;KACtL,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CACxB,GAAG,CAAC,KAAK,IAAI,EAAE;QACb,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAqC,gBAAgB,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QACtH,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM;YAAE,OAAO,4CAA4C,IAAI,wEAAwE,CAAC;QACvJ,OAAO;YACL,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,wBAAwB,IAAI,IAAI;YACvE,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;SACxL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CACL,CAAC;IACF,GAAG,CACD,YAAY,EACZ;QACE,WAAW,EAAE,qZAAqZ;QACla,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+DAA+D,CAAC;YAC5F,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,QAAQ,CAAC,kDAAkD,CAAC;YAC/G,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;YACtB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;YACd,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;YACnB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC7B,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;YACrD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;SACnE,CAAC;KACH,EACD,KAAK,EAAE,CAAC,EAAE,EAAE,CACV,GAAG,CAAC,KAAK,IAAI,EAAE;QACb,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAmB,aAAa,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9G,OAAO,SAAS,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,0DAA0D,CAAC;IAChG,CAAC,CAAC,CACL,CAAC;AACJ,CAAC"}
@@ -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;
@@ -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
- const channels = d.io_csv ? parseIoMappingCsv(d.io_csv) : [];
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":"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"}
@@ -2,7 +2,13 @@ import type { Screen, Tone } from "./spec.js";
2
2
  import { type SymbolLib } from "./symbols.js";
3
3
  type Argb = number;
4
4
  export interface NativeElement {
5
- kind: "rect" | "round" | "circle" | "switcher" | "image";
5
+ kind: "rect" | "round" | "circle" | "switcher" | "image" | "complex";
6
+ /** complex: CODESYS's element type, e.g. VisuFbElemMeter180 (made through the visual editor, so only with CODESYS's window). */
7
+ type_name?: string;
8
+ /** complex: properties in the order to set them. */
9
+ pairs?: [string, string | number | boolean][];
10
+ /** complex: what to draw instead when CODESYS has no window. */
11
+ fallback?: NativeElement[];
6
12
  name?: string;
7
13
  x: number;
8
14
  y: number;
@@ -83,8 +89,14 @@ export declare function symbols(p: Palette): {
83
89
  id: string;
84
90
  svg: string;
85
91
  }[];
92
+ /**
93
+ * alarmIndex: each HMI alarm's position in the CODESYS alarm group AG_HMI (when the export put
94
+ * them there); those rows then follow CODESYS's alarm states and can be acknowledged.
95
+ */
86
96
  export declare function nativePlan(screen: Screen, pages?: {
87
97
  name: string;
88
98
  title: string;
89
- }[], custom?: SymbolLib): NativePlan;
99
+ }[], custom?: SymbolLib, alarmIndex?: Record<string, number>): NativePlan;
100
+ /** Pictures per gauge: one every 2.5 % of the range. */
101
+ export declare const GAUGE_STEPS = 40;
90
102
  export {};
@@ -92,7 +92,11 @@ ${on ? `<circle cx="50" cy="50" r="46" fill="none" stroke="${ring}" stroke-width
92
92
  { id: "valve_closed_v", svg: valve(false).replace(/(<svg[^>]*>)([\s\S]*)(<\/svg>)/, '$1<g transform="rotate(90 50 50)">$2</g>$3') },
93
93
  ];
94
94
  }
95
- export function nativePlan(screen, pages = [], custom = {}) {
95
+ /**
96
+ * alarmIndex: each HMI alarm's position in the CODESYS alarm group AG_HMI (when the export put
97
+ * them there); those rows then follow CODESYS's alarm states and can be acknowledged.
98
+ */
99
+ export function nativePlan(screen, pages = [], custom = {}, alarmIndex) {
96
100
  const p = PALETTES[screen.theme];
97
101
  const lib = { ...BUILTIN_SYMBOLS, ...custom };
98
102
  const dark = screen.theme === "dark";
@@ -174,11 +178,39 @@ export function nativePlan(screen, pages = [], custom = {}) {
174
178
  }));
175
179
  break;
176
180
  }
177
- case "gauge":
181
+ case "gauge": {
182
+ // The browser's arc gauge as pictures: one per GAUGE_STEPS of the range, in the band's
183
+ // colour, chosen by the helper program; the value and limits are text on top.
184
+ const s = Math.max(80, Math.min(box.w, (box.h - 6) * 1.16));
185
+ const gw = r(s), gh = r(s * 0.86);
186
+ const gx = box.x + (box.w - gw) / 2, gy = box.y + Math.max(0, (box.h - gh) / 2);
187
+ const idx = `iG${++n}`;
188
+ decl.push(` ${idx} : INT; // gauge ${w.value}`);
189
+ impl.push(`${idx} := REAL_TO_INT(LIMIT(0.0, (ANY_TO_REAL(${w.value}) - (${num(w.min)})) / (${num(w.max - w.min || 1)}), 1.0) * ${GAUGE_STEPS}.0);`);
190
+ const ids = [];
191
+ for (let k = 0; k <= GAUGE_STEPS; k++) {
192
+ const id = `gauge_${gw}_${k}${w.bands?.length ? `_${bandKey(w.bands)}` : ""}`;
193
+ if (!needed.has(id))
194
+ needed.set(id, gaugeSvg(p, k / GAUGE_STEPS, toneAt(w.bands, w.min + (k / GAUGE_STEPS) * (w.max - w.min)), gw, gh));
195
+ ids.push(id);
196
+ }
197
+ const prefix = `gauge_${gw}_`;
198
+ const suffix = w.bands?.length ? `_${bandKey(w.bands)}` : "";
199
+ els.push({ kind: "image", x: r(gx), y: r(gy), w: gw, h: gh, props: { "Static ID": `${pool}.${ids[0]}`, "Bitmap ID variable.Bitmap ID": `CONCAT('${pool}.${prefix}', CONCAT(INT_TO_STRING(${helperName}.${idx}), '${suffix}'))` } });
200
+ const hot = band(w.bands);
201
+ els.push(text(gx, gy + gh * 0.44, gw, gh * 0.2, fmt(w.decimals), [SEMI, Math.max(12, Math.min(28, gw * 0.12))], p.text, "HCENTER", {
202
+ props: { "Text variables.Text variable": real(w.value), ...(hot ? { "Color variables.Toggle color": above(w.value, hot.from), "Text properties.Font alarm color": c(p.tones[hot.tone]) } : {}) },
203
+ }));
204
+ if (w.unit)
205
+ els.push(text(gx, gy + gh * 0.64, gw, gh * 0.12, w.unit, [FONT, Math.max(8, Math.min(12, gw * 0.055))], p.muted, "HCENTER"));
206
+ els.push(text(gx + gw * 0.08, gy + gh * 0.86, gw * 0.3, gh * 0.12, String(w.min), [FONT, 8], p.muted));
207
+ els.push(text(gx + gw * 0.62, gy + gh * 0.86, gw * 0.3, gh * 0.12, String(w.max), [FONT, 8], p.muted, "RIGHT"));
208
+ break;
209
+ }
178
210
  case "bar": {
179
211
  const hot = band(w.bands);
180
212
  const size = Math.max(16, Math.min(40, box.h * 0.3));
181
- els.push(text(box.x, box.y, box.w, box.h * 0.5, `${fmt(w.type === "gauge" ? w.decimals : 1)}${w.unit ? ` ${w.unit}` : ""}`, [LIGHT, size], p.text, w.type === "gauge" ? "HCENTER" : "LEFT", { props: { "Text variables.Text variable": real(w.value) } }));
213
+ els.push(text(box.x, box.y, box.w, box.h * 0.5, `${fmt(1)}${w.unit ? ` ${w.unit}` : ""}`, [LIGHT, size], p.text, "LEFT", { props: { "Text variables.Text variable": real(w.value) } }));
182
214
  const ty = box.y + box.h * 0.62;
183
215
  const len = r(box.w);
184
216
  els.push({ kind: "round", x: r(box.x), y: r(ty), w: len, h: 14, fill: p.track, frame: null, radius: 7 });
@@ -257,11 +289,37 @@ export function nativePlan(screen, pages = [], custom = {}) {
257
289
  }
258
290
  case "alarms": {
259
291
  const rowH = Math.min(34, (box.h - 6 * (w.items.length - 1)) / w.items.length);
260
- els.push(text(box.x, box.y, box.w, 24, "No active alarms", [FONT, 10], p.tones.ok, "LEFT", { invisible: w.items.map((a) => a.active).join(" OR ") }));
292
+ const open = [];
293
+ const rows = [];
261
294
  w.items.forEach((a, k) => {
262
295
  const tone = a.severity === "info" ? "accent" : a.severity === "warning" ? "warn" : "alarm";
263
- els.push({ kind: "round", x: r(box.x), y: r(box.y + k * (rowH + 6)), w: r(box.w), h: r(rowH), text: ` ${a.text}`, font: [SEMI, 9.5], color: p.tones[tone], align: "LEFT", fill: p.soft[tone], frame: p.tones[tone], radius: 8, invisible: `NOT ${a.active}` });
296
+ const y = box.y + k * (rowH + 6);
297
+ const idx = alarmIndex?.[a.active];
298
+ if (idx === undefined) {
299
+ // Not a CODESYS alarm (yet): the row shows while the variable is TRUE.
300
+ open.push(a.active);
301
+ rows.push(() => els.push({ kind: "round", x: r(box.x), y: r(y), w: r(box.w), h: r(rowH), text: ` ${a.text}`, font: [SEMI, 9.5], color: p.tones[tone], align: "LEFT", fill: p.soft[tone], frame: p.tones[tone], radius: 8, invisible: `NOT ${a.active}` }));
302
+ return;
303
+ }
304
+ // A CODESYS alarm: the row stays until it is acknowledged, with its state, time and ACK.
305
+ const al = `__Alm__GVL.g_inst.gen_Inst_AG_HMI_AlarmGroup.gen_ArrAllAlarms[${idx}]`;
306
+ const m = ++n;
307
+ decl.push(` iAs${m} : INT; // alarm ${a.active}`, ` xAo${m} : BOOL;`, ` xAk${m} : BOOL;`, ` sAs${m} : STRING(24);`, ` sAt${m} : STRING(12);`);
308
+ impl.push(`iAs${m} := TO_INT(${al}.m_eState);`, `xAo${m} := iAs${m} <> 0;`, `xAk${m} := iAs${m} = 2 OR iAs${m} = 3 OR iAs${m} = 5;`, `CASE iAs${m} OF 1: sAs${m} := 'pending'; 2: sAs${m} := 'ACTIVE'; 3: sAs${m} := 'cleared - confirm'; 4: sAs${m} := 'acknowledged'; 5: sAs${m} := 'cleared - acknowledge'; ELSE sAs${m} := ''; END_CASE`,
309
+ // m_liTimestampActive is a FILETIME (100 ns since 1601, UTC); m_iOffsetActive the local offset in minutes.
310
+ `IF xAo${m} THEN sAt${m} := RIGHT(DT_TO_STRING(LINT_TO_DT(${al}.m_liTimestampActive / 10000000 - 11644473600 + INT_TO_LINT(${al}.m_iOffsetActive) * 60)), 8); END_IF`);
311
+ open.push(`${helperName}.xAo${m}`);
312
+ rows.push(() => {
313
+ const hide = `NOT ${helperName}.xAo${m}`;
314
+ els.push({ kind: "round", x: r(box.x), y: r(y), w: r(box.w), h: r(rowH), fill: p.soft[tone], frame: p.tones[tone], radius: 8, invisible: hide });
315
+ els.push(text(box.x + 12, y, 70, rowH, "%s", [SEMI, 9], p.muted, "LEFT", { invisible: hide, props: { "Text variables.Text variable": `${helperName}.sAt${m}` } }));
316
+ els.push(text(box.x + 84, y, box.w - 270, rowH, a.text, [SEMI, 9.5], p.tones[tone], "LEFT", { invisible: hide }));
317
+ els.push(text(box.x + box.w - 186, y, 120, rowH, "%s", [SEMI, 8.5], p.tones[tone], "RIGHT", { invisible: hide, props: { "Text variables.Text variable": `${helperName}.sAs${m}` } }));
318
+ els.push({ kind: "round", x: r(box.x + box.w - 58), y: r(y + 4), w: 50, h: r(rowH - 8), text: "ACK", font: [SEMI, 9], color: 0xffffffff, fill: p.tones[tone], frame: p.tones[tone], radius: 6, invisible: `NOT ${helperName}.xAk${m}`, click: `${al}.m_xToAcknowledge := TRUE;` });
319
+ });
264
320
  });
321
+ els.push(text(box.x, box.y, box.w, 24, "No active alarms", [FONT, 10], p.tones.ok, "LEFT", { invisible: open.join(" OR ") }));
322
+ rows.forEach((f) => f());
265
323
  break;
266
324
  }
267
325
  case "text":
@@ -475,6 +533,40 @@ export function nativePlan(screen, pages = [], custom = {}) {
475
533
  };
476
534
  }
477
535
  const SERIES = [0xff38bdf8, 0xffa78bfa, 0xfffbbf24, 0xff34d399, 0xfff472b6, 0xff60a5fa];
536
+ /** Pictures per gauge: one every 2.5 % of the range. */
537
+ export const GAUGE_STEPS = 40;
538
+ /** The tone of a value by its bands (the last band it reaches), accent without bands. */
539
+ function toneAt(bands, v) {
540
+ let t = "accent";
541
+ for (const b of [...(bands ?? [])].sort((a, b2) => a.from - b2.from))
542
+ if (v >= b.from)
543
+ t = b.tone;
544
+ return t;
545
+ }
546
+ /** A short key for a band set, so gauges with different bands get their own pictures. */
547
+ function bandKey(bands) {
548
+ let h = 0;
549
+ for (const ch of JSON.stringify(bands))
550
+ h = (h * 31 + ch.charCodeAt(0)) >>> 0;
551
+ return h.toString(36).slice(0, 5);
552
+ }
553
+ /** The arc gauge of the browser HMI at fraction f of its range (200 x 172 box, drawn at w x h). */
554
+ function gaugeSvg(p, f, tone, w, h) {
555
+ const R = 80;
556
+ const pt = (a) => [100 + R * Math.sin((a * Math.PI) / 180), 100 - R * Math.cos((a * Math.PI) / 180)].map((v) => Math.round(v * 10) / 10);
557
+ const arc = (a0, a1) => {
558
+ const [x0, y0] = pt(a0), [x1, y1] = pt(a1);
559
+ return `M${x0} ${y0} A${R} ${R} 0 ${a1 - a0 > 180 ? 1 : 0} 1 ${x1} ${y1}`;
560
+ };
561
+ const col = hex(p.tones[tone]);
562
+ const ticks = Array.from({ length: 11 }, (_, i) => {
563
+ const a = ((-120 + i * 24) * Math.PI) / 180;
564
+ const r0 = i % 5 === 0 ? 62 : 66;
565
+ return `<line x1="${(100 + r0 * Math.sin(a)).toFixed(1)}" y1="${(100 - r0 * Math.cos(a)).toFixed(1)}" x2="${(100 + 70 * Math.sin(a)).toFixed(1)}" y2="${(100 - 70 * Math.cos(a)).toFixed(1)}"/>`;
566
+ }).join("");
567
+ const value = f > 0.001 ? `<path d="${arc(-120, -120 + 240 * f)}" fill="none" stroke="${col}" stroke-opacity=".35" stroke-width="22" stroke-linecap="round"/><path d="${arc(-120, -120 + 240 * f)}" fill="none" stroke="${col}" stroke-width="14" stroke-linecap="round"/>` : "";
568
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 200 172"><path d="${arc(-120, 120)}" fill="none" stroke="${hex(p.track)}" stroke-width="14" stroke-linecap="round"/>${value}<g stroke="${hex(p.muted)}" stroke-width="1.5" opacity=".6">${ticks}</g></svg>`;
569
+ }
478
570
  function num(x) {
479
571
  return Number.isInteger(x) ? `${x}.0` : String(x);
480
572
  }