dsh-plc-decode 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,30 +1,42 @@
1
1
  # dsh-plc-decode
2
2
 
3
+ <img src="assets/icon-180.png" width="96" align="right" alt="dsh-plc-decode">
4
+
3
5
  [中文](README.md) | **English**
4
6
 
5
- Read PLC project files directly inside [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH). The plugin detects the format from the file header, decodes the project into readable reports and structured data, and provides project diffing, address audits, element-level read/write analysis and ladder-logic reconstruction.
7
+ Read PLC project files directly inside [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): the plugin detects the format from the file header, decodes the project into readable reports and structured data, and provides project diffing, address audits, element-level read/write analysis and ladder-logic reconstruction. The same plugin row also covers HMI projects: screens, controls, tags and addresses; for Weintek EasyBuilder Pro it reads the compiled output of the project and decodes screens, text objects, alarm events and text-library entries.
6
8
 
7
9
  **Read-only**: project files are parsed, never modified.
8
10
 
9
- ## Supported formats
11
+ ## Supported files
10
12
 
11
13
  | Header | Format | Decoder |
12
14
  |---|---|---|
13
15
  | `50 4B 03 04` (ZIP) | CoDeSys V3.5 / LeadSysStudio `.project` (incl. Leadshine) | Bundled Python script: unpack → string table → restore POU ST sources |
14
- | `57 01 01 00` | Delta WPLSoft / ISPSoft `.dvp` | Bundled Python script: structure / section comments / device comments / instruction table |
15
- | `53 48 33 00` (SH3) | Siemens S7-200 SMART `.smart` | Embedded Node decoder: blocks / instruction records / operands / user & system symbol tables |
16
+ | `57 01 01 00` | Delta WPLSoft / ISPSoft `.dvp` | Bundled Python script: file structure / section comments / device comments / instruction table |
17
+ | `53 48 33 00` (SH3) | Siemens S7-200 SMART `.smart` | Embedded Node decoder: blocks / instruction records / operands / user and system symbol tables |
16
18
  | `44 45 4D 00` (DEM) | Siemens S7-200 SMART `.smartlib` | Embedded Node decoder: library variables and comment stream |
17
19
 
18
- > Leadshine LeadSysStudio saves CoDeSys-format `.project` files, so they decode through the same path: ST sources go to `pous/<POU>.txt`, and `/codesys-check` runs an equivalent compile check (struct members, array bounds, calls, address uniqueness, undeclared symbols).
20
+ > Leadshine LeadSysStudio saves CoDeSys-format `.project` files; they decode through the same path: ST sources go to `pous/<POU>.txt`, and `/codesys-check` runs an equivalent compile check (struct members, array bounds, calls, address uniqueness, undeclared symbols).
21
+
22
+ ### HMI projects
23
+
24
+ | File | Brand | Decoded |
25
+ |---|---|---|
26
+ | `.hmismart` | Siemens WinCC flexible SMART (Smart Line) | Screens / objects / texts / devices; the sibling `{{GUID}}.data` files yield tag names, control names, binding candidates and the type table |
27
+ | `.fsprj` or a folder containing `HMI\Frame` | Flexem FStudio | Screens / controls / control addresses (from `HMI\System\fs2.s3db` and `Bin\fshmi.bin`) |
28
+ | `.nbp` | Omron NB-Designer | Screens / controls / addresses (XML) |
29
+ | `.mcp` / `.mcgs` | MCGS / LSTP Studio | Screens / tags / alarms / strategies (Access database) |
30
+ | `.emtp` / `.exob` | Weintek EasyBuilder Pro | Screens / text objects / alarm events / text library (the `.emtp` body is encrypted; the plugin reads the compiled `.exob` of the same project) |
19
31
 
20
32
  ## Install
21
33
 
22
34
  Prerequisites:
23
35
 
24
- - DSH desktop app (the plugin runs inside DSH; Node version follows DSH);
36
+ - DSH desktop app (the plugin runs inside DSH; the Node version follows DSH);
25
37
  - Python 3.9+ on `PATH` for `.project` / `.dvp` (standard library only, no `pip install`);
26
- - Python is not needed for `.smart` / `.smartlib`.
27
- - Requires DSH >= 0.2.1-alpha.3; verified on 0.2.1-alpha.3 and 0.2.2-rc.1, later versions untested.
38
+ - Python is not needed for `.smart` / `.smartlib`;
39
+ - requires DSH >= 0.2.1-alpha.3; verified on 0.2.1-alpha.3 and 0.2.2-rc.1, later versions untested.
28
40
 
29
41
  ```sh
30
42
  dsh plugin --profile web add github:taoziliyouchong/dsh-plc-decode
@@ -38,48 +50,124 @@ Restart DSH afterwards. The plugin has no npm runtime dependencies.
38
50
 
39
51
  | Tool | Purpose |
40
52
  |---|---|
41
- | `plc-decode` | Unified entry: detect the four formats and dispatch; args `file`, optional `outDir` |
53
+ | `plc-decode` | Unified entry: detect the four PLC formats and dispatch; args `file`, optional `outDir` |
42
54
  | `smart-pou-structure` | Internal structure of one POU: interface parameters (L-area address + type), box-level instruction chain, call sites and arguments |
43
- | `smart-lad` | Ladder reconstruction: per block/network LAD instruction sequence (LD/A/O/AN/ON, EU/ED, LPS/LRD/LPP, coils, comparisons, instruction-box placeholders) |
55
+ | `smart-lad` | Ladder reconstruction: per block and network, the LAD instruction sequence (LD/A/O/AN/ON, EU/ED, LPS/LRD/LPP, coils, comparisons, instruction-box placeholders) |
44
56
  | `smart-ladder-elements` | Element-level read/write judgement for one address (or a batch) |
45
57
  | `smart-net-trace` | Network trace: elements, write coils, boxes and constants in offset order |
46
58
  | `smart-box-pins` | Box pin table: pin name = actual argument for library call boxes (MBUS_CTRL, MBUS_MSG, TON, CTU, MOV_*, …) |
47
- | `smart-pin-dump` | Byte-level location: all occurrences of an address, or operand records in an offset range |
48
- | `smart-var-usage` | Variable audit: all references, overlapping byte forms, write-point judgement |
49
- | `smart-free-space` | Address usage and free ranges: pick new addresses when extending a program |
59
+ | `smart-pin-dump` | Byte-level location: all occurrences of an address, or the operand records inside an offset range |
60
+ | `smart-var-usage` | Variable audit: all references, other address forms over the same bytes, write-point judgement |
61
+ | `smart-free-space` | Address usage and free ranges: pick new addresses before extending a program |
50
62
  | `smart-conflict` | Address conflicts: overlaps (real / suspected / registered only) and fragmented free space |
51
63
  | `smart-diff-project` | SMART project diff: block list, call chain, I/O references, symbol table |
52
64
  | `codesys-diff-project` | CoDeSys project diff: POU list, real logic changes, whitespace, string table, object table |
65
+ | `hmi-decode` | HMI unified entry: identifies `.hmismart` / `.fsprj` / `.nbp` / `.mcp` / `.emtp` / `.exob` and processes it |
66
+ | `hmi-diff-project` | HMI project diff for two `.hmismart` files: start screen, screen records, screen names, companion files |
67
+ | `ebpro-decode` | Weintek EasyBuilder Pro: decode screens / text objects / alarm events / text library from the compiled `.exob` |
68
+ | `ladder-auto` | Auto ladder: read a `.smart` project and render box-style text ladders per block/network (`block`, `net`) |
69
+ | `ladder-dvp` | Delta `.dvp` → text ladder, optionally limited to a line range |
70
+ | `ladder-from-stl` | Statement list (STL) → text ladder plus a mechanical explanation per network |
71
+ | `ladder-render` | Render a network description (spec JSON) into the fixed text-ladder layout |
53
72
 
54
73
  ### Slash commands
55
74
 
56
- `/plc-decode`, `/codesys-read`, `/dvp-read`, `/smart-read`, `/codesys-check`, `/codesys-diff`, `/smart-diff`, `/smart-free-space`, `/smart-conflict`, `/smart-ladder`, `/smart-lad`.
75
+ | Command | Purpose |
76
+ |---|---|
77
+ | `/plc-decode <file> [outDir]` | Unified decode entry |
78
+ | `/plc-archive` | Show the effective archive root, the default target and how to change it |
79
+ | `/codesys-read <project>` | Decode a `.project` (ST sources + report) or a `.dvp` (report) |
80
+ | `/dvp-read <project>` | Delta DVP structure and instruction-table report |
81
+ | `/smart-read <.smart/.smartlib> [outDir]` | Decode a SMART project or library |
82
+ | `/codesys-check <project>` | Decode a `.project`, then run an equivalent static compile check |
83
+ | `/codesys-diff <old> <new>` | CoDeSys project diff |
84
+ | `/smart-diff <old> <new>` | SMART project diff |
85
+ | `/smart-free-space <.smart>` | Address usage and free ranges |
86
+ | `/smart-conflict <.smart>` | Address conflict audit |
87
+ | `/smart-ladder <.smart> [address]` | Element-level read/write judgement |
88
+ | `/smart-lad <.smart> [block] [network]` | Ladder reconstruction (LAD instruction sequence) |
89
+ | `/hmi-decode <.hmismart / .fsprj / .nbp / .mcp / .emtp / .exob> [outDir]` | HMI project decode |
90
+ | `/hmi-diff <old .hmismart> <new .hmismart>` | HMI project diff |
91
+ | `/ladder on\|off\|status` | Ladder switch (off by default; takes effect immediately) |
92
+ | `/ladder-auto <.smart> [block] [net] [out.txt]` | Render a `.smart` project |
93
+ | `/ladder-dvp <.dvp> [from] [to] [out.txt]` | Render a `.dvp` project |
94
+ | `/ladder-stl <stl.txt> [out.txt]` | Statement list → ladder + explanation |
95
+ | `/ladder-spec <spec.json> [out.txt]` | Render a spec JSON |
96
+ | `/plc-calibrate <format> <bytes or cmd:XX> <name> [note]` | Record an unresolved byte/opcode in the calibration table; later decodes use the name |
57
97
 
58
98
  ## Output
59
99
 
60
- Decoded files go to `解码归档/<project folder name>/` (the literal folder name is Chinese for "decode archive"; it lives in the current workspace). Use `outDir` to override.
100
+ By default the plugin writes to `解码归档/<project folder name>/` in the **workspace** (the folder name is used rather than the file name, so same-named projects in different folders do not overwrite each other; when one folder holds several project files, `<folder>_<file>` is used). Pass `outDir` to override.
101
+
102
+ | Format | Main files |
103
+ |---|---|
104
+ | `.project` | `00-索引.md` (read this first), `pous/<POU>.txt` (ST sources), `工程内容报告.md`, `全部字符串.txt`, `对象表.md`, `项目树.md` |
105
+ | `.dvp` | `00-索引.md`, `解码报告.md` (structure / section comments / device comments / instruction table) |
106
+ | `.smart` / `.smartlib` | `00-目录.md` (read this first), `解码报告.md`, `程序.lad`, `元素记录.tsv`, `符号地址.tsv`, `分析.md`, `记录.json`, `字符串全录.txt` |
107
+ | `.hmismart` | `10-画面.tsv`, `11-元件.tsv`, `12-文本.tsv`, `13-设备.tsv`, `20-变量.data.tsv`, `21-控件.data.tsv`, `23-控件绑定.data.tsv`, `24-类型表.tsv` |
108
+ | `.fsprj` | `画面.tsv`, `控件.tsv`, `控件地址.tsv`, `变量.tsv` |
109
+ | `.nbp` / `.mcp` | `解码报告.md` plus screen/control (or tag/alarm/strategy) JSON |
110
+ | `.emtp` / `.exob` | `HMI解码/HMI解码报告.md` + `00-格式说明.md`, `10-画面.tsv`, `11-文字对象.tsv`, `12-报警事件.tsv`, `13-文字库.tsv`, `08-统计.json` |
111
+
112
+ ### Ladder rendering (off by default)
61
113
 
62
- - `.project` → `00-索引.md`, `pous/<POU>.txt`, `工程内容报告.md`, `全部字符串.txt`, `对象表.md`, `项目树.md`
63
- - `.dvp` → `00-索引.md`, `解码报告.md`
64
- - `.smart` / `.smartlib` → `00-目录.md`, `解码报告.md`, `程序.lad`, `元素记录.tsv`, `符号地址.tsv`, `分析.md`, `记录.json`, `字符串全录.txt`
114
+ Ladder tools are disabled by default so a plain decode does not add another set of tools:
65
115
 
66
- The smart decoder writes a source fingerprint (size + mtime) into the report and `记录.json`; re-check it after the project is edited.
116
+ - Turn on: `/ladder on` (immediate, no restart); off: `/ladder off`; status: `/ladder status`
117
+ - Or set `config: { ladder: true }` on this plugin's row in the profile `cordis.patch.yml` as the initial state
118
+ - While disabled, the ladder tools answer with a one-line hint; decoding is unaffected
119
+ - The first three decodes append a hint that ladder diagrams are available (1/3, 2/3, 3/3), then it stops; switch state and the counter live in `$DSH_HOME/ladder-state.json`
67
120
 
68
- ## Limitations
121
+ ### Changing the archive root (no code change)
69
122
 
70
- - HMI projects (`.hmismart`, `.fsprj`, `.nbp`, `.mcp`) are decoded by the same plugin row: screens / controls / tags / addresses, tables written to `解码归档/<project>/`. Control-to-tag references in `.hmismart` are partial (only same-segment and UUID hits); the rest is reported as "未解出".
71
- - Pin-level arguments of library call boxes (Slave / RW / Addr / Count / DataPtr) cannot be decoded; the tools say so instead of guessing.
123
+ Add `archiveDir` to this plugin's row in the profile `cordis.patch.yml` (`$DSH_HOME/profiles/<profile>/cordis.patch.yml`):
124
+
125
+ `archiveDir` is the archive **root**: a relative path resolves against the current session workspace, an absolute path is used as is; the default is `解码归档`. Restart DSH after changing it; `/plc-archive` shows the effective value, and `outDir` overrides it for a single run.
126
+
127
+ The smart `记录.json` is the input of the analysis tools (address usage, variable usage, …) and also the marker that identifies a decoded directory. The report header carries a **source fingerprint** (file size + mtime); re-check it after the project changes.
128
+
129
+ ## Known limits
130
+
131
+ - HMI projects: `.hmismart` carries only the template skeleton — real screens and controls live in the sibling `{{GUID}}.data` (ES2RT stream). Tag names and control names can be decoded; control-to-tag references are recovered only for same-segment co-occurrence and UUID hits, the rest is reported as undecoded. Without tags, a FStudio project keeps control addresses only in the action table of `Bin\fshmi.bin`. MCGS decoding needs a sample to verify against.
132
+ - Weintek EasyBuilder Pro: the `.emtp` body is compressed and encrypted, so only the compiled `.exob` of the same project can be read; the tables reflect the project **at compile time** (recompile first if the project changed). The compiled output carries only the source language, no translations. Screen membership is inferred from object-stream segments; x/y/w/h in the tables are text-box metrics, not screen coordinates.
133
+ - Pin-level arguments of library call boxes (e.g. Slave / RW / Addr / Count / DataPtr of MBUS_MSG) cannot be decoded; the tools say so instead of guessing.
72
134
  - Encrypted or specially compressed projects may fail to decode; the decoder reports an error rather than a half-true result.
73
135
  - Ladder series/parallel topology is reconstructed from instruction records (validated network-by-network against official STL exports); library call regions without standard box headers are not covered.
74
- - Inferred instruction names are marked with `?`; undecoded names keep their raw form `??xx`.
136
+ - Inferred instruction names are marked with `?`; undecoded names keep the raw form `??xx`.
75
137
 
76
138
  ## Development
77
139
 
78
140
  ```sh
79
- npm run check # syntax check
80
- npm run smoke # load the plugin and verify registrations (12 tools / 11 commands)
81
- npm run smoke -- <project> # decode one real project end-to-end
82
- npm pack --dry-run # inspect package contents
141
+ npm run check # syntax check
142
+ npm run smoke # load the plugin and verify registrations (19 tools / 20 commands)
143
+ npm run smoke -- <project> # additionally decode one real project end-to-end
144
+ npm pack --dry-run # inspect package contents (node_modules and publish/ must not be inside)
145
+ ```
146
+
147
+ ## Structure
148
+
149
+ ```
150
+ index.js plugin entry: tool and command registration, format dispatch, SMART decoder
151
+ hmi.js HMI decoding module (tools/commands/skill registration, called by index.js)
152
+ ladder.js ladder rendering module (off by default, /ladder on; called by index.js)
153
+ assets/decode-pack.mjs decoded-output writer (indexes, tables, records)
154
+ assets/smart-lad.mjs ladder reconstruction script (used by smart-lad)
155
+ assets/smart-net-trace.mjs network trace script (used by smart-net-trace)
156
+ assets/codesys-tools/ CoDeSys / DVP decoding Python scripts (standard library only)
157
+ assets/hmismart-decode.mjs Siemens .hmismart decoder (structure tables + .data names/bindings)
158
+ assets/hmismart-report.mjs .hmismart human-readable report (screens/tags/connections)
159
+ assets/hmismart-diff.mjs .hmismart project diff
160
+ assets/fstudio-tables.py FStudio screen / control / control-address tables
161
+ assets/fstudio_i18n.py FStudio element-text scan (per language slot)
162
+ assets/hmi-decode-workflow.md HMI reading workflow (skill text)
163
+ assets/nb-decode.mjs Omron NB-Designer decoder
164
+ assets/mcgs-decode.mjs MCGS / LSTP decoder
165
+ assets/ebpro-decode.mjs Weintek EasyBuilder Pro decoder (from .exob)
166
+ assets/ladder-render.mjs text-ladder renderer (used by ladder-render)
167
+ assets/stl-to-ladder.mjs STL → ladder (used by ladder-from-stl)
168
+ assets/smart-ladder-auto.mjs .smart auto renderer (used by ladder-auto)
169
+ assets/dvp-ladder.mjs .dvp auto renderer (used by ladder-dvp)
170
+ scripts/smoke.mjs smoke test
83
171
  ```
84
172
 
85
173
  ## Feedback
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **中文** | [English](README.en.md)
6
6
 
7
- 在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)里直接读 PLC 工程文件:按文件头自动识别格式,把工程解成可读报告与结构化数据,再基于解码结果做新旧对比、地址体检、元素级读/写判定与连线还原。同一插件行里也承接触摸屏(HMI)工程:画面、控件、变量与地址。
7
+ 在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)里直接读 PLC 工程文件:按文件头自动识别格式,把工程解成可读报告与结构化数据,再基于解码结果做新旧对比、地址体检、元素级读/写判定与连线还原。同一插件行里也承接触摸屏(HMI)工程:画面、控件、变量与地址;威纶通 EasyBuilder Pro 读该工程的编译产物,解出画面、文字对象、报警事件与文字库。
8
8
 
9
9
  **只读**:解析工程文件,不改动原文件。
10
10
 
@@ -27,6 +27,7 @@
27
27
  | `.fsprj` 或含 `HMI\Frame` 的目录 | 繁易 FStudio / Flexem | 画面 / 控件 / 控件地址(读 `HMI\System\fs2.s3db` 与 `Bin\fshmi.bin`) |
28
28
  | `.nbp` | 欧姆龙 NB-Designer | 画面 / 控件 / 地址(XML 直读) |
29
29
  | `.mcp` / `.mcgs` | 昆仑通态 MCGS / LSTP Studio | 画面 / 变量 / 报警 / 策略(Access 库) |
30
+ | `.emtp` / `.exob` | 威纶通 EasyBuilder Pro | 画面 / 文字对象 / 报警事件 / 文字库(`.emtp` 本体加密,读同工程的编译产物 `.exob`) |
30
31
 
31
32
  ## 安装
32
33
 
@@ -34,8 +35,8 @@
34
35
 
35
36
  - DSH 桌面端(插件运行在 DSH 内,Node 版本跟随 DSH);
36
37
  - 解码 `.project` / `.dvp` 需要 Python 3.9+ 在 `PATH` 里(只用标准库,无需 pip 安装);
37
- - 解码 `.smart` / `.smartlib` 不需要 Python。
38
- - 兼容 DSH 0.2.1-alpha.3 ~ 0.2.2-rc.1(本机实际运行验证过的最高版本);更高版本未验证。
38
+ - 解码 `.smart` / `.smartlib` 不需要 Python;
39
+ - 要求 DSH >= 0.2.1-alpha.3;实测 0.2.1-alpha.3、0.2.2-rc.1,更高版本未验证。
39
40
 
40
41
  从 GitHub 安装:
41
42
 
@@ -63,12 +64,13 @@ dsh plugin --profile web add github:taoziliyouchong/dsh-plc-decode
63
64
  | `smart-conflict` | 地址冲突体检:重叠冲突(真冲突 / 疑似 / 仅登记)+ 连续空间被切碎 |
64
65
  | `smart-diff-project` | SMART 新旧工程对比:块清单、主程序调用链、I-O 引用、符号表差异 |
65
66
  | `codesys-diff-project` | CoDeSys 新旧工程对比:POU 清单、真实逻辑变化区段、空白差异、字符串表、对象表 |
66
- | `hmi-decode` | 触摸屏统一入口:按文件识别 `.hmismart` / `.fsprj` / `.nbp` / `.mcp` 并解码 |
67
+ | `hmi-decode` | 触摸屏统一入口:按文件识别 `.hmismart` / `.fsprj` / `.nbp` / `.mcp` / `.emtp` / `.exob` 并解码 |
67
68
  | `hmi-diff-project` | 新旧 `.hmismart` 对比:起始画面、画面表、画面名、配套文件 |
68
- | `fstudio-scan-texts` | 繁易工程元件文本扫描(按语种槽) |
69
- | `fstudio-i18n` | 繁易多语言对照表与文本库导入表 |
70
- | `fstudio-retag` | 已导出文本库表换语种列 |
71
- | `wincc-text-i18n` | WinCC 项目文本三件套(Screens / Others / Messages)翻译与回写 |
69
+ | `ebpro-decode` | 威纶通 EasyBuilder Pro:从编译产物 `.exob` 解画面 / 文字对象 / 报警事件 / 文字库 |
70
+ | `ladder-auto` | 自动出梯形图:直接读 `.smart` 工程,按块(可单网络)生成盒式文字梯形图(`block`、`net`) |
71
+ | `ladder-dvp` | 台达 `.dvp` 工程 → 文字梯形图,可只出某几行 |
72
+ | `ladder-from-stl` | 语句表(STL)→ 文字梯形图 + 每个网络的机械讲解 |
73
+ | `ladder-render` | 把网络描述(spec JSON)渲染成固定版式的文字梯形图 |
72
74
 
73
75
  ### 斜杠命令(人直接执行)
74
76
 
@@ -86,9 +88,14 @@ dsh plugin --profile web add github:taoziliyouchong/dsh-plc-decode
86
88
  | `/smart-conflict <.smart>` | 地址冲突体检 |
87
89
  | `/smart-ladder <.smart> [地址]` | 元素级读/写判定 |
88
90
  | `/smart-lad <.smart> [块名] [网络号]` | 连线还原(LAD 指令序列) |
89
- | `/hmi-decode <.hmismart / .fsprj / .nbp / .mcp> [输出目录]` | 触摸屏工程解码 |
91
+ | `/hmi-decode <.hmismart / .fsprj / .nbp / .mcp / .emtp / .exob> [输出目录]` | 触摸屏工程解码 |
90
92
  | `/hmi-diff <旧 .hmismart> <新 .hmismart>` | 触摸屏新旧对比 |
91
- | `/fstudio-retag <TextLib 表> <词典> [输出]` | 文本库表换语种列 |
93
+ | `/ladder on\|off\|status` | 梯形图开关(默认关闭,立即生效) |
94
+ | `/ladder-auto <.smart> [块名] [网络号] [out.txt]` | 出 `.smart` 的梯形图 |
95
+ | `/ladder-dvp <.dvp> [起行] [止行] [out.txt]` | 出 `.dvp` 的梯形图 |
96
+ | `/ladder-stl <stl.txt> [out.txt]` | 语句表 → 梯形图 + 讲解 |
97
+ | `/ladder-spec <spec.json> [out.txt]` | 渲染 spec JSON |
98
+ | `/plc-calibrate <格式> <字节或 cmd:XX> <名字> [备注]` | 把解不出的字节 / 功能码记进校准表,之后解码直接用这个名字 |
92
99
 
93
100
  ## 输出位置
94
101
 
@@ -102,12 +109,21 @@ dsh plugin --profile web add github:taoziliyouchong/dsh-plc-decode
102
109
  | `.hmismart` | `10-画面.tsv`、`11-元件.tsv`、`12-文本.tsv`、`13-设备.tsv`、`20-变量.data.tsv`、`21-控件.data.tsv`、`23-控件绑定.data.tsv`、`24-类型表.tsv` |
103
110
  | `.fsprj` | `画面.tsv`、`控件.tsv`、`控件地址.tsv`、`变量.tsv` |
104
111
  | `.nbp` / `.mcp` | `解码报告.md` + 画面/控件(或变量/报警/策略)JSON |
112
+ | `.emtp` / `.exob` | `HMI解码/HMI解码报告.md` + `00-格式说明.md`、`10-画面.tsv`、`11-文字对象.tsv`、`12-报警事件.tsv`、`13-文字库.tsv`、`08-统计.json` |
113
+
114
+ ### 梯形图(默认关闭)
115
+
116
+ 梯形图工具默认关着,免得一次普通解码就多出一组工具:
117
+
118
+ - 打开:`/ladder on`(立即生效,不用重启);关闭:`/ladder off`;看状态:`/ladder status`
119
+ - 或者在 profile 的 `cordis.patch.yml` 里给本插件那一行写 `config: { ladder: true }` 作初始状态
120
+ - 关着时梯形图工具只回一行开启提示;解码照常
121
+ - 前三次解码会在结果末尾提示「可以看梯形图」(1/3、2/3、3/3),之后不再提示;开关状态与计数存在 `$DSH_HOME/ladder-state.json`
105
122
 
106
123
  ### 改归档地址(不用改代码)
107
124
 
108
125
  在 DSH profile 的 `cordis.patch.yml`(`$DSH_HOME/profiles/<profile>/cordis.patch.yml`)末尾给本插件那一行加 config(只写 `archiveDir`:归档根目录):
109
126
 
110
-
111
127
  `archiveDir` 是归档**根目录**:相对路径按当前会话工作区解析,绝对路径原样使用;默认 `解码归档`。改完重启 dsh 生效,`/plc-archive` 可随时查看当前生效值;只想改一次就在解码时传 `outDir`。
112
128
 
113
129
  `.smart` 的 `记录.json` 同时是各分析工具(地址占用、变量使用等)的输入,也是识别「已解码目录」的凭据;解码报告头部带**源文件指纹**(文件大小 + 修改时间),工程换版本后先核对指纹再用旧报告。
@@ -115,6 +131,7 @@ dsh plugin --profile web add github:taoziliyouchong/dsh-plc-decode
115
131
  ## 已知边界
116
132
 
117
133
  - 触摸屏工程:`.hmismart` 里只有模板骨架,真实画面与控件在旁系 `{{GUID}}.data`(ES2RT 序列化流),变量名与控件名能解,控件到变量的引用只解出同段共现与 UUID 命中那部分,其余标「未解出」;繁易工程若没建变量,控件地址只在 `Bin\fshmi.bin` 的动作表里;昆仑通态工程需本机有 `.mcp` 样本才能验证。
134
+ - 威纶通 EasyBuilder Pro:`.emtp` 本体压缩加密,只能读同工程的编译产物 `.exob`,表里反映的是**编译当时**的工程(工程改过要先重新编译);编译产物只带源语言,没有译文。画面归属由对象流分段推定,表里的 x/y/w/h 是文字框度量,不是画面坐标。
118
135
  - 库块调用区(如 MBUS_MSG 的 Slave / RW / Addr / Count / DataPtr)的引脚级实参解不出,工具会明确说明,不猜。
119
136
  - 加密或特殊压缩的工程可能解不出;解码器会报错而不是给出半真结果。
120
137
  - `.smart` 的串/并联拓扑由指令记录还原,已用官方导出的 STL 逐网络回归验证;库调用区(无标准盒头)不在覆盖范围内。
@@ -124,7 +141,7 @@ dsh plugin --profile web add github:taoziliyouchong/dsh-plc-decode
124
141
 
125
142
  ```sh
126
143
  npm run check # 语法检查
127
- npm run smoke # 装载插件 + 核对注册面(工具 18 个 / 命令 15 个)
144
+ npm run smoke # 装载插件 + 核对注册面(工具 19 个 / 命令 20 个)
128
145
  npm run smoke -- <工程文件> # 额外对一个真实工程跑端到端解码,核对产物落盘
129
146
  npm pack --dry-run # 看包里内容(node_modules、publish/ 不应进包)
130
147
  ```
@@ -134,16 +151,24 @@ npm pack --dry-run # 看包里内容(node_modules、publish/ 不应
134
151
  ```
135
152
  index.js 插件入口:工具与命令注册、四格式分发、SMART 解码器
136
153
  hmi.js 触摸屏解码模块(工具/命令/技能注册,由 index.js 调用)
137
- invariant.js 伴随插件:登记包名所有权
154
+ ladder.js 梯形图模块(默认关闭,/ladder on;由 index.js 调用)
138
155
  assets/decode-pack.mjs 解码产物落盘(索引/目录/表格/记录)
139
156
  assets/smart-lad.mjs 连线还原脚本(由 smart-lad 工具调用)
140
157
  assets/smart-net-trace.mjs 网络轨迹脚本(由 smart-net-trace 工具调用)
141
158
  assets/codesys-tools/ CoDeSys / DVP 解码 Python 脚本(只用标准库)
142
159
  assets/hmismart-decode.mjs 西门子 .hmismart 解码器(结构表 + .data 名字/绑定/类型表)
160
+ assets/hmismart-report.mjs .hmismart 可读报告(画面/变量/连接)
143
161
  assets/hmismart-diff.mjs .hmismart 新旧对比
144
162
  assets/fstudio-tables.py 繁易画面 / 控件 / 控件地址表
163
+ assets/fstudio_i18n.py 繁易元件文本扫描(按语种槽)
164
+ assets/hmi-decode-workflow.md 触摸屏解读流程(技能正文)
145
165
  assets/nb-decode.mjs 欧姆龙 NB-Designer 解码器
146
166
  assets/mcgs-decode.mjs 昆仑通态 MCGS / LSTP 解码器
167
+ assets/ebpro-decode.mjs 威纶通 EasyBuilder Pro 解码器(读 .exob)
168
+ assets/ladder-render.mjs 文字梯形图渲染器(由 ladder-render 调用)
169
+ assets/stl-to-ladder.mjs STL → 梯形图(由 ladder-from-stl 调用)
170
+ assets/smart-ladder-auto.mjs .smart 自动出图(由 ladder-auto 调用)
171
+ assets/dvp-ladder.mjs .dvp 自动出图(由 ladder-dvp 调用)
147
172
  scripts/smoke.mjs 冒烟测试
148
173
  ```
149
174
 
@@ -3,7 +3,7 @@
3
3
  dvp_decode.py — 台达 WPLSoft/ISPSoft DVP 工程文件解码器
4
4
 
5
5
  用法:
6
- python dvp_decode.py "C:\\projects\\demo.dvp" [-o 输出目录]
6
+ python dvp_decode.py <工程文件.dvp> [-o 输出目录]
7
7
 
8
8
  功能:
9
9
  1. 文件结构(机型 / TypeBegin 块 / SMT 块分段)
@@ -33,6 +33,7 @@ dvp_decode.py — 台达 WPLSoft/ISPSoft DVP 工程文件解码器
33
33
  import sys
34
34
  import os
35
35
  import re
36
+ import json
36
37
 
37
38
  def read_bytes(path):
38
39
  with open(path, "rb") as f:
@@ -113,6 +114,7 @@ ROWIDX_PREFIX = {0x14, 0x16}
113
114
  # (段号, 行号, 字节原文),main 末尾聚合成「未识别记录」一节。
114
115
  # 这不是猜语义,是把「读不出来」变成看得见的账——用户能据此对照 WPLSoft 指令表。
115
116
  UNKNOWN_RECS = []
117
+ CALIB = {}
116
118
 
117
119
  # 触点/线圈指令前缀:
118
120
  # 12 触点族(LD/AND/T 页)、32=OUT、22=AND/ANI
@@ -328,7 +330,7 @@ def _to_loading(cp):
328
330
  return cp
329
331
 
330
332
 
331
- def disasm(seg, segno=0):
333
+ def disasm(seg, segno=0, collect=None, calib=None):
332
334
  """段数据区 → 行分组指令 token 流
333
335
  - 比较指令族 ?CMP(e2/e4/e6/e9 + 双操作数,指令名待对照,统一 ? 前缀)
334
336
  - 行尾/分隔记录整体吞 5B(0b/61 10/68 10/57 10/5a/34/39)
@@ -403,7 +405,7 @@ def disasm(seg, segno=0):
403
405
  # 段首非程序记录(不在任何网络里、无操作数),不是指令,整体吞 5B
404
406
  i += 5
405
407
  continue
406
- toks.append(CMD_MAP.get(rec[2]) or guess_cmd(rec[2]) or f"??{rec[2]:02X}")
408
+ toks.append(_cal_name(calib, f"cmd:{rec[2]:02X}") or CMD_MAP.get(rec[2]) or guess_cmd(rec[2]) or f"??{rec[2]:02X}")
407
409
  i += 5
408
410
  else:
409
411
  op = decode_operand(rec)
@@ -446,8 +448,41 @@ def disasm(seg, segno=0):
446
448
  toks.append(f"〔?{rec[0]:02x}{rec[1]:02x}{rec[2]:02x}〕")
447
449
  i += step
448
450
  rows.append(" ".join(toks))
451
+ if collect is not None:
452
+ collect.append((segno, li, list(toks)))
449
453
  return rows
450
454
 
455
+
456
+ # ---- 指令流结构化(--json 输出给梯形图用;与报告同一份 token) ----
457
+ _OPERAND_RE = re.compile(r'^(?:K-?\d+|H[0-9A-F]+|[DXMYTCSV]\d+(?:\.\d+)?|F-?[\d.]+)$')
458
+
459
+ def _cal_name(calib, key):
460
+ v = (calib or {}).get(key)
461
+ if isinstance(v, dict):
462
+ v = v.get("name")
463
+ return v
464
+
465
+
466
+ def to_insts(toks):
467
+ """token 串 → [{op,args}]:操作数挂到前一条指令上;| 分隔符丢弃。"""
468
+ insts = []
469
+ for t in toks:
470
+ if t == '|' or (t.startswith('|H') and t.endswith('|')):
471
+ continue
472
+ if t in ('MPS', 'MRD', 'MPP', 'ORB', 'ANB') or t.startswith('〔?') or t.startswith('?CMP') or t.startswith('??'):
473
+ insts.append({'op': t, 'args': []})
474
+ continue
475
+ if ' ' in t:
476
+ op, _, arg = t.partition(' ')
477
+ insts.append({'op': op, 'args': [arg]})
478
+ continue
479
+ if _OPERAND_RE.match(t):
480
+ if insts:
481
+ insts[-1]['args'].append(t)
482
+ continue
483
+ insts.append({'op': t, 'args': []})
484
+ return insts
485
+
451
486
  def main():
452
487
  argv = sys.argv[1:]
453
488
  if not argv:
@@ -455,13 +490,22 @@ def main():
455
490
  return
456
491
  path = argv[0]
457
492
  out_dir = None
493
+ json_path = argv[argv.index("--json") + 1] if "--json" in argv else None
494
+ calib_path = argv[argv.index("--calib") + 1] if "--calib" in argv else None
458
495
  if "-o" in argv:
459
496
  out_dir = argv[argv.index("-o") + 1]
460
497
 
461
498
  data = read_bytes(path)
462
499
  text = data.decode("ascii", "replace")
463
500
  UNKNOWN_RECS.clear()
501
+ if calib_path and os.path.exists(calib_path):
502
+ try:
503
+ with open(calib_path, encoding="utf-8") as f:
504
+ CALIB.update((json.load(f).get("dvp") or {}))
505
+ except Exception:
506
+ pass
464
507
  lines = []
508
+ insts_out = []
465
509
 
466
510
  # ---- 1. 结构 ----
467
511
  lines.append("=" * 60)
@@ -539,7 +583,7 @@ def main():
539
583
  spans.append((0, de0 + len("<DataEnd>"), nxt))
540
584
  lines.append(" (本工程无 <SMT> 分段标记,按 Initial Value 块后的整块数据反汇编)")
541
585
  for si, ds, de in spans:
542
- rows = disasm(data[ds:de], si)
586
+ rows = disasm(data[ds:de], si, insts_out, CALIB)
543
587
  lines.append(f"--- 段{si} ---")
544
588
  for li, r in enumerate(rows):
545
589
  lines.append(f" 行{li}: {r}")
@@ -574,6 +618,11 @@ def main():
574
618
  lines.append(" 比较族 ?CMP(关系):尾字节 00 = / 02 <> / 04 > / 06 < / 08 <= / 0a >=;08、0a 已核对,其余按步长 2 推断。")
575
619
  lines.append(" 仍未定:以 ?? 开头的功能码(形态不足以推断)、以 ?CMP[ 开头的带码占位(关系码不在上表)。")
576
620
 
621
+ if json_path:
622
+ os.makedirs(os.path.dirname(os.path.abspath(json_path)), exist_ok=True)
623
+ with open(json_path, "w", encoding="utf-8") as f:
624
+ json.dump([{"seg": sg, "line": li, "insts": to_insts(toks)} for sg, li, toks in insts_out], f, ensure_ascii=False)
625
+
577
626
  report = "\n".join(lines)
578
627
  if out_dir:
579
628
  os.makedirs(out_dir, exist_ok=True)
@@ -3,7 +3,7 @@
3
3
  read_project.py — 读取 CoDeSys V3.5 / LeadSysStudio 工程文件 (.project) 的内容。
4
4
 
5
5
  用法:
6
- python read_project.py "C:\\projects\\demo.project" [-o 输出目录]
6
+ python read_project.py <工程文件.project> [-o 输出目录]
7
7
 
8
8
  功能:
9
9
  1. 解包 .project (ZIP)
@@ -73,7 +73,7 @@ export function writeSourcePack({ outd, kind, projName, projFile }) {
73
73
  if (has('项目树.md')) nav += `| 项目树.md | ${num(sizeOf('项目树.md'))} B | AI | 项目树 |\n`
74
74
  if (kind === 'dvp') {
75
75
  nav += `\n## 怎么查(按问题挑一条,别整份读)\n\n`
76
- nav += `- 某段指令表 → 按下文「网络行号表」取段(行号=解码报告里的行,从 1 起)\n`
76
+ nav += `- 某段梯形图 → \`ladder-dvp\` from=<段号> to=<段号>(段号=网络号,从 1 起;不是报告行号)\n`
77
77
  nav += `- 某地址在哪些网络出现 → grep 地址(如 \`grep -n "D2340" 解码报告.md\`),只读命中的那几行\n`
78
78
  nav += `- 找中文注释/设备名 → grep 解码报告.md(dvp 只出这一份文本,不生成 全部字符串.txt)\n\n`
79
79
  nav += `## 读法\n\n- **看程序**:台达 DVP 没有 pous/ST,程序就是 \`解码报告.md\` 的指令表(按 \`LD M1000\` 分网络,末尾 END)。\n- **AI**:目录里有 \`程序解读.md\` 就先读它(网络级功能地图+I/O 表+机制链+未确证清单),再回指令表逐段核对。报告里 \`?名\`(如 \`?DRVI\`)与 \`?CMP(关系)\` 是**推断**指令名——报告末尾有「推断对照表」(码/推测名/依据/把握),拿它下结论前先核台达指令表;\`??xx\` 与 \`?CMP[六位]\` 是推断不出、保持未定的。\n- 读之前核对源工程文件修改时间是否与本次解码一致(工程一改,导出即过期)。\n`
@@ -88,7 +88,7 @@ export function writeSourcePack({ outd, kind, projName, projFile }) {
88
88
  }
89
89
  if (starts.length) {
90
90
  const CAP = 50
91
- nav += `\n## 网络定位(共 ${starts.length} 个网络;要哪段用 按行号取段,别整份读)\n\n| # | 起始行 | 首条 |\n|---|---|---|\n`
91
+ nav += `\n## 网络定位(共 ${starts.length} 个网络;要哪段用 ladder-dvp 按段号取(=上表 #),别整份读)\n\n| # | 报告行 | 首条 |\n|---|---|---|\n`
92
92
  for (let k = 0; k < Math.min(starts.length, CAP); k++) {
93
93
  const from = starts[k]
94
94
  const head = (lines[from - 1] || '').trim().replace(/^行\s*\d+\s*[::]\s*/, '').slice(0, 46).replace(/\|/g, '\\|')