dsh-plugin-pscad-expert-vyma 1.0.8 → 1.0.10
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.md +4 -4
- package/index.js +120 -3
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/tools/cli.py +5 -0
- package/tools/main.py +35 -12
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pscad-dsh-plugin — PSCAD 自动化建模与仿真控制专家插件
|
|
2
2
|
|
|
3
|
-
> **版本**:1.0.
|
|
3
|
+
> **版本**:1.0.10 | **注册名**:`pscad-automation-expert`
|
|
4
4
|
> **来源**:PSCAD 知识资产固化项目(四阶段:技能文档 → 模板索引 → 工具库 → 插件打包)
|
|
5
5
|
> 所有知识、工具、模板均来自真实仿真验证(L13~L19 认证轮 + R4 科研轮),**无伪造数据**。
|
|
6
6
|
|
|
@@ -25,8 +25,8 @@ pscad-dsh-plugin/
|
|
|
25
25
|
└── templates/ # 已验证成功运行的基准 .pscx 拓扑(6 个)
|
|
26
26
|
├── grid_gfm_support.pscx # 并网 GFM 支撑基准(容量/位置扫描)
|
|
27
27
|
├── fault_study.pscx # 故障研究基准(3LG/1LG/2LG 可控故障支路)
|
|
28
|
-
├──
|
|
29
|
-
├──
|
|
28
|
+
├── island_droop_controller.pscx # 孤岛下垂 + Fortran 自定义控制器基准
|
|
29
|
+
├── line_model_pi_100km.pscx # source3 单线图 + newpi 线路(Ferranti 对照)
|
|
30
30
|
├── gfm_island_droop_nose.pscx # 双 GFM 孤岛下垂 + 鼻点扫描(历史经典)
|
|
31
31
|
└── three_phase_cable_fault.pscx # 三相电缆故障(Cable_Coax 专项)
|
|
32
32
|
```
|
|
@@ -59,7 +59,7 @@ Agent 维持运行 Loop。全函数 Type Hints + 中文 Docstring。
|
|
|
59
59
|
含中段母线 Vmid 与受端母线 Vr 双接入点。
|
|
60
60
|
- **fault_study**:在上述架构上加可控故障支路(breaker1+Rf+tbreak,TC=0.3/TO=0.4),
|
|
61
61
|
3LG/1LG/2LG 只需改 tbreak 参数(每类独立案例);短路电流已与解析公式 <1% 对照。
|
|
62
|
-
- **
|
|
62
|
+
- **island_droop_controller**:孤岛 GFM + 工程内嵌 Fortran 自定义组件 DROOPC(饱和下垂+虚拟惯量),
|
|
63
63
|
研究变量集中在 DROOPC 参数(M/F0/T/FMAX/FMIN)。
|
|
64
64
|
- **line_model_pi_100km**:source3 单线图 + newpi 线路空载(Ferranti 效应/线路充电对照)。
|
|
65
65
|
- **gfm_island_droop_nose**:双 GFM 孤岛下垂 + 鼻点族判稳(经典同步稳定性验证)。
|
package/index.js
CHANGED
|
@@ -15,27 +15,104 @@
|
|
|
15
15
|
|
|
16
16
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
17
17
|
import Schema from '@deepseek-ai/schemastery'
|
|
18
|
-
import { spawn } from 'node:child_process'
|
|
18
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
19
19
|
import { fileURLToPath } from 'node:url'
|
|
20
|
+
import fs from 'node:fs'
|
|
21
|
+
import path from 'node:path'
|
|
20
22
|
|
|
21
23
|
export const name = 'pscad-expert-bridge'
|
|
22
24
|
export const inject = ['tools']
|
|
23
25
|
|
|
24
26
|
// ---------------- 插件配置(Schemastery,无硬编码) ----------------
|
|
25
27
|
export const Config = Schema.object({
|
|
26
|
-
pythonExecutable: Schema.string().default('python')
|
|
28
|
+
pythonExecutable: Schema.string().default('python')
|
|
29
|
+
.description('Python 解释器路径;默认自动探测(PATH→py 启动器→常见安装位置),无需手动配置'),
|
|
27
30
|
toolsCli: Schema.string().default('./tools/cli.py'),
|
|
28
31
|
defaultFolder: Schema.string().default(''),
|
|
29
32
|
timeoutMs: Schema.number().default(120000),
|
|
30
33
|
})
|
|
31
34
|
|
|
35
|
+
// ---------------- Python 解释器自动探测(免 profile YAML 配置) ----------------
|
|
36
|
+
let _resolvedPython = null
|
|
37
|
+
|
|
38
|
+
function pyVersionOk(exe) {
|
|
39
|
+
try {
|
|
40
|
+
const r = spawnSync(exe, ['-c', 'import sys; print(sys.version_info[0], sys.version_info[1])'], {
|
|
41
|
+
timeout: 10000, windowsHide: true, encoding: 'utf8',
|
|
42
|
+
})
|
|
43
|
+
if (r.status !== 0 || r.error) return false
|
|
44
|
+
const m = /^(\d+)\s+(\d+)/.exec((r.stdout || '').trim())
|
|
45
|
+
return !!m && Number(m[1]) >= 3 && Number(m[2]) >= 7
|
|
46
|
+
} catch { return false }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function findPythonInDir(parent, prefix) {
|
|
50
|
+
// 从目录名解析次版本号(Python3xx),升序优先旧版:
|
|
51
|
+
// 越接近 3.7 越稳妥(mhi.zip 2.2.1 面向 Python 3.7,且依赖 3.12 已移除的 distutils)
|
|
52
|
+
try {
|
|
53
|
+
const dirs = fs.readdirSync(parent, { withFileTypes: true })
|
|
54
|
+
.filter(e => e.isDirectory() && e.name.startsWith(prefix))
|
|
55
|
+
.map(e => ({ name: e.name, minor: parseInt(e.name.slice(prefix.length), 10) || 999 }))
|
|
56
|
+
.sort((a, b) => a.minor - b.minor)
|
|
57
|
+
for (const d of dirs) {
|
|
58
|
+
const exe = path.join(parent, d.name, 'python.exe')
|
|
59
|
+
if (fs.existsSync(exe)) return exe
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolvePython(config) {
|
|
66
|
+
if (_resolvedPython) return _resolvedPython
|
|
67
|
+
// 用户显式配置(非默认 'python')优先,尊重覆盖
|
|
68
|
+
if (config.pythonExecutable && config.pythonExecutable !== 'python') {
|
|
69
|
+
_resolvedPython = config.pythonExecutable
|
|
70
|
+
return _resolvedPython
|
|
71
|
+
}
|
|
72
|
+
// 1) PATH 上的 python:验证是真实 3.7+(排除微软商店占位 stub,其退出码 9009/2)
|
|
73
|
+
if (pyVersionOk('python')) { _resolvedPython = 'python'; return _resolvedPython }
|
|
74
|
+
// 2) py 启动器枚举已安装版本:取最接近 3.7 的可用版本
|
|
75
|
+
try {
|
|
76
|
+
const r = spawnSync('py', ['-0p'], { timeout: 10000, windowsHide: true, encoding: 'utf8' })
|
|
77
|
+
if (r.status === 0) {
|
|
78
|
+
const picks = []
|
|
79
|
+
for (const line of (r.stdout || '').split(/\r?\n/)) {
|
|
80
|
+
// 兼容含空格路径(如 C:\Program Files\Python37\python.exe)
|
|
81
|
+
const m = /-V:(\d+)\.(\d+)\s+\*?\s*(.+\\python\.exe)\s*$/.exec(line)
|
|
82
|
+
if (m && fs.existsSync(m[3])) {
|
|
83
|
+
const v = Number(m[1]) * 100 + Number(m[2])
|
|
84
|
+
if (Number(m[1]) >= 3 && v >= 307) picks.push({ v, exe: m[3] })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (picks.length) {
|
|
88
|
+
// 优先 Python 3.7–3.11(mhi.zip 2.2.1 依赖 distutils,3.12+ 不可导入);
|
|
89
|
+
// 若机器只有 3.12+,退而用之(配合 pip 安装的 mhi-pscad 新版仍可用)
|
|
90
|
+
const le311 = picks.filter(p => p.v <= 311).sort((a, b) => a.v - b.v)
|
|
91
|
+
const gt311 = picks.filter(p => p.v > 311).sort((a, b) => a.v - b.v)
|
|
92
|
+
_resolvedPython = (le311.length ? le311[0] : gt311[0]).exe
|
|
93
|
+
return _resolvedPython
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch {}
|
|
97
|
+
// 3) 常见安装位置(Program Files / 用户级 / 盘符根)
|
|
98
|
+
const home = process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || '', 'AppData', 'Local')
|
|
99
|
+
const found = findPythonInDir('C:/Program Files', 'Python3') ||
|
|
100
|
+
findPythonInDir(path.join(home, 'Programs', 'Python'), 'Python3') ||
|
|
101
|
+
findPythonInDir('C:/', 'Python3')
|
|
102
|
+
if (found) { _resolvedPython = found; return _resolvedPython }
|
|
103
|
+
// 4) 兜底:保持默认(若 PATH python 为 stub,错误会自然暴露并有明确提示)
|
|
104
|
+
_resolvedPython = 'python'
|
|
105
|
+
return _resolvedPython
|
|
106
|
+
}
|
|
107
|
+
|
|
32
108
|
// ---------------- Python 桥接(子进程 JSON-RPC,支持取消) ----------------
|
|
33
109
|
function runPy(tool, args, config, signal) {
|
|
34
110
|
const cliPath = fileURLToPath(new URL(config.toolsCli, import.meta.url))
|
|
111
|
+
const python = resolvePython(config)
|
|
35
112
|
return new Promise((resolve, reject) => {
|
|
36
113
|
let settled = false
|
|
37
114
|
const done = (fn, v) => { if (!settled) { settled = true; fn(v) } }
|
|
38
|
-
const proc = spawn(
|
|
115
|
+
const proc = spawn(python, [cliPath, tool, JSON.stringify(args)], {
|
|
39
116
|
windowsHide: true,
|
|
40
117
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
41
118
|
})
|
|
@@ -171,6 +248,46 @@ const toolDefs = [
|
|
|
171
248
|
description: 'PSCAD 插件环境自检:配置状态、路径存在性、工具清单。诊断用。',
|
|
172
249
|
parameters: {},
|
|
173
250
|
},
|
|
251
|
+
{
|
|
252
|
+
name: 'pscad_register_pgb',
|
|
253
|
+
description: '注册 PGB 输出通道(放置 pgb + 同名 datalabel;通道名与目标信号同名即记录该信号)。',
|
|
254
|
+
parameters: {
|
|
255
|
+
channel_name: { type: 'string', required: true, description: '输出通道名(与目标信号同名)' },
|
|
256
|
+
x: { type: 'integer', description: 'pgb 锚点 X;缺省自动布点' },
|
|
257
|
+
y: { type: 'integer', description: 'pgb 锚点 Y;缺省自动布点' },
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'pscad_configure_simulation',
|
|
262
|
+
description: '配置当前工程仿真参数(时长/EMTDC 步长/采样步长/输出文件名)。短线路 time_step 须 < 传播时间/10。',
|
|
263
|
+
parameters: {
|
|
264
|
+
time_duration: { type: 'number', description: '仿真时长(秒,默认 0.5)' },
|
|
265
|
+
time_step: { type: 'number', description: 'EMTDC 步长(微秒,默认 10)' },
|
|
266
|
+
sample_step: { type: 'number', description: '输出采样步长(微秒,默认 50)' },
|
|
267
|
+
output_filename: { type: 'string', description: '输出文件名(不含路径,写入 .gf46 目录)' },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: 'pscad_compute_sequence',
|
|
272
|
+
description: 'Fortescue 对称分量换算:由三相电流幅值/相角计算正负零序分量。',
|
|
273
|
+
parameters: {
|
|
274
|
+
ia: { type: 'number', required: true, description: 'A 相电流(相量)' },
|
|
275
|
+
ib: { type: 'number', required: true, description: 'B 相电流(相量)' },
|
|
276
|
+
ic: { type: 'number', required: true, description: 'C 相电流(相量)' },
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'pscad_restart_pscad',
|
|
281
|
+
description: '强制重启 PSCAD 自动化服务(卡死/10054 崩溃恢复):终止进程、重新启动并重连、重载当前工程。',
|
|
282
|
+
parameters: {},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: 'pscad_check_component_overlaps',
|
|
286
|
+
description: '布局自检:遍历画布全部组件检查锚点/端口重叠(build 前诊断)。',
|
|
287
|
+
parameters: {
|
|
288
|
+
min_gap: { type: 'integer', description: '最小间距(默认 2)' },
|
|
289
|
+
},
|
|
290
|
+
},
|
|
174
291
|
]
|
|
175
292
|
|
|
176
293
|
// ---------------- 插件主体 ----------------
|
package/package.json
CHANGED
package/plugin.yaml
CHANGED
package/tools/cli.py
CHANGED
|
@@ -83,6 +83,11 @@ TOOL_WHITELIST: Dict[str, Any] = {
|
|
|
83
83
|
'pscad_run_simulation': _run_simulation_configure,
|
|
84
84
|
'pscad_read_output': pat.read_output,
|
|
85
85
|
'pscad_self_check': pat.self_check,
|
|
86
|
+
'pscad_register_pgb': pat.register_pgb,
|
|
87
|
+
'pscad_configure_simulation': pat.configure_simulation,
|
|
88
|
+
'pscad_compute_sequence': pat.compute_sequence,
|
|
89
|
+
'pscad_restart_pscad': pat.restart_pscad,
|
|
90
|
+
'pscad_check_component_overlaps': pat.check_component_overlaps,
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
|
package/tools/main.py
CHANGED
|
@@ -352,26 +352,49 @@ def _require_config() -> Optional[Dict[str, Any]]:
|
|
|
352
352
|
return CONFIG_MISSING_RESPONSE
|
|
353
353
|
|
|
354
354
|
|
|
355
|
+
def _mhi_import_error(exc: Exception, pylib: str) -> str:
|
|
356
|
+
"""构造 mhi 导入失败消息;针对 Python ≥3.12 + distutils 给出明确降级指引。"""
|
|
357
|
+
ver = '%d.%d' % sys.version_info[:2]
|
|
358
|
+
hint = ''
|
|
359
|
+
if sys.version_info >= (3, 12) and 'distutils' in str(exc):
|
|
360
|
+
hint = ('。PSCAD 自带 mhi.zip 为 mhi.pscad 2.2.1,依赖 Python 3.12 已移除的 '
|
|
361
|
+
'distutils,仅支持 Python ≤3.11:请改用 ≤3.11 的 Python 作为桥接解释器,'
|
|
362
|
+
'或执行 pip install mhi-pscad 安装新版(无此依赖)')
|
|
363
|
+
return ('import mhi.pscad 失败(Python %s,pylib=%s): %s%s'
|
|
364
|
+
% (ver, pylib, exc, hint))
|
|
365
|
+
|
|
366
|
+
|
|
355
367
|
def _import_mhi() -> Tuple[bool, str]:
|
|
356
|
-
"""懒加载 mhi.pscad
|
|
368
|
+
"""懒加载 mhi.pscad:多候选导入,**绝不抛异常**。
|
|
357
369
|
|
|
358
|
-
|
|
359
|
-
|
|
370
|
+
顺序:① 已加载则直接绑定返回;② site-packages 直接导入(pip 安装的
|
|
371
|
+
mhi-pscad 新版天然可导入);③ 将配置/自动发现的 pylib(目录或 mhi.zip)
|
|
372
|
+
加入 sys.path 后导入。全部失败返回带指引的消息(Python ≥3.12 时提示
|
|
373
|
+
distutils 兼容问题与降级方案)。
|
|
360
374
|
"""
|
|
361
375
|
global mhi # 必须绑定到模块级:连接代码(_ensure_connected/_launch_pscad)
|
|
362
376
|
# 依赖 mhi.pscad.connect(...),函数内 import 只会绑定局部名导致 NameError
|
|
363
|
-
pylib = _cfg_pylib()
|
|
364
|
-
if not pylib:
|
|
365
|
-
return False, MISSING_CONFIG_MSG
|
|
366
377
|
try:
|
|
367
|
-
if 'mhi.pscad'
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
378
|
+
if 'mhi.pscad' in sys.modules:
|
|
379
|
+
import mhi.pscad # noqa: F401 幂等绑定模块级名字
|
|
380
|
+
return True, ''
|
|
381
|
+
# ② 先试 site-packages / 已有 sys.path(pip 版 mhi-pscad)
|
|
382
|
+
try:
|
|
383
|
+
import mhi.pscad # noqa: F401
|
|
384
|
+
return True, ''
|
|
385
|
+
except Exception:
|
|
386
|
+
pass
|
|
387
|
+
# ③ 候选 pylib(配置优先,_cfg_pylib 内部已含自动发现回退)
|
|
388
|
+
pylib = _cfg_pylib()
|
|
389
|
+
if not pylib:
|
|
390
|
+
return False, MISSING_CONFIG_MSG
|
|
391
|
+
if pylib not in sys.path:
|
|
392
|
+
sys.path.insert(0, pylib)
|
|
393
|
+
import mhi.pscad # noqa: F401
|
|
371
394
|
return True, ''
|
|
372
395
|
except Exception as exc:
|
|
373
|
-
|
|
374
|
-
|
|
396
|
+
pylib = _cfg_pylib() or ''
|
|
397
|
+
return False, _mhi_import_error(exc, pylib)
|
|
375
398
|
|
|
376
399
|
|
|
377
400
|
# ---------------------------------------------------------------------------
|