dsh-plugin-pscad-expert-vyma 1.0.9 → 1.0.11
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 +52 -6
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/tools/cli.py +5 -0
- package/tools/main.py +79 -20
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pscad-dsh-plugin — PSCAD 自动化建模与仿真控制专家插件
|
|
2
2
|
|
|
3
|
-
> **版本**:1.0.
|
|
3
|
+
> **版本**:1.0.11 | **注册名**:`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
|
@@ -29,7 +29,8 @@ export const Config = Schema.object({
|
|
|
29
29
|
.description('Python 解释器路径;默认自动探测(PATH→py 启动器→常见安装位置),无需手动配置'),
|
|
30
30
|
toolsCli: Schema.string().default('./tools/cli.py'),
|
|
31
31
|
defaultFolder: Schema.string().default(''),
|
|
32
|
-
timeoutMs: Schema.number().default(
|
|
32
|
+
timeoutMs: Schema.number().default(300000)
|
|
33
|
+
.description('单次工具调用总超时(毫秒);PSCAD 冷启动等待上限 180s,需留余量'),
|
|
33
34
|
})
|
|
34
35
|
|
|
35
36
|
// ---------------- Python 解释器自动探测(免 profile YAML 配置) ----------------
|
|
@@ -47,13 +48,15 @@ function pyVersionOk(exe) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function findPythonInDir(parent, prefix) {
|
|
50
|
-
//
|
|
51
|
+
// 从目录名解析次版本号(Python3xx),升序优先旧版:
|
|
52
|
+
// 越接近 3.7 越稳妥(mhi.zip 2.2.1 面向 Python 3.7,且依赖 3.12 已移除的 distutils)
|
|
51
53
|
try {
|
|
52
54
|
const dirs = fs.readdirSync(parent, { withFileTypes: true })
|
|
53
55
|
.filter(e => e.isDirectory() && e.name.startsWith(prefix))
|
|
54
|
-
.map(e => e.name
|
|
56
|
+
.map(e => ({ name: e.name, minor: parseInt(e.name.slice(prefix.length), 10) || 999 }))
|
|
57
|
+
.sort((a, b) => a.minor - b.minor)
|
|
55
58
|
for (const d of dirs) {
|
|
56
|
-
const exe = path.join(parent, d, 'python.exe')
|
|
59
|
+
const exe = path.join(parent, d.name, 'python.exe')
|
|
57
60
|
if (fs.existsSync(exe)) return exe
|
|
58
61
|
}
|
|
59
62
|
} catch {}
|
|
@@ -83,8 +86,11 @@ function resolvePython(config) {
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
if (picks.length) {
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
// 优先 Python 3.7–3.11(mhi.zip 2.2.1 依赖 distutils,3.12+ 不可导入);
|
|
90
|
+
// 若机器只有 3.12+,退而用之(配合 pip 安装的 mhi-pscad 新版仍可用)
|
|
91
|
+
const le311 = picks.filter(p => p.v <= 311).sort((a, b) => a.v - b.v)
|
|
92
|
+
const gt311 = picks.filter(p => p.v > 311).sort((a, b) => a.v - b.v)
|
|
93
|
+
_resolvedPython = (le311.length ? le311[0] : gt311[0]).exe
|
|
88
94
|
return _resolvedPython
|
|
89
95
|
}
|
|
90
96
|
}
|
|
@@ -243,6 +249,46 @@ const toolDefs = [
|
|
|
243
249
|
description: 'PSCAD 插件环境自检:配置状态、路径存在性、工具清单。诊断用。',
|
|
244
250
|
parameters: {},
|
|
245
251
|
},
|
|
252
|
+
{
|
|
253
|
+
name: 'pscad_register_pgb',
|
|
254
|
+
description: '注册 PGB 输出通道(放置 pgb + 同名 datalabel;通道名与目标信号同名即记录该信号)。',
|
|
255
|
+
parameters: {
|
|
256
|
+
channel_name: { type: 'string', required: true, description: '输出通道名(与目标信号同名)' },
|
|
257
|
+
x: { type: 'integer', description: 'pgb 锚点 X;缺省自动布点' },
|
|
258
|
+
y: { type: 'integer', description: 'pgb 锚点 Y;缺省自动布点' },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: 'pscad_configure_simulation',
|
|
263
|
+
description: '配置当前工程仿真参数(时长/EMTDC 步长/采样步长/输出文件名)。短线路 time_step 须 < 传播时间/10。',
|
|
264
|
+
parameters: {
|
|
265
|
+
time_duration: { type: 'number', description: '仿真时长(秒,默认 0.5)' },
|
|
266
|
+
time_step: { type: 'number', description: 'EMTDC 步长(微秒,默认 10)' },
|
|
267
|
+
sample_step: { type: 'number', description: '输出采样步长(微秒,默认 50)' },
|
|
268
|
+
output_filename: { type: 'string', description: '输出文件名(不含路径,写入 .gf46 目录)' },
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'pscad_compute_sequence',
|
|
273
|
+
description: 'Fortescue 对称分量换算:由三相电流幅值/相角计算正负零序分量。',
|
|
274
|
+
parameters: {
|
|
275
|
+
ia: { type: 'number', required: true, description: 'A 相电流(相量)' },
|
|
276
|
+
ib: { type: 'number', required: true, description: 'B 相电流(相量)' },
|
|
277
|
+
ic: { type: 'number', required: true, description: 'C 相电流(相量)' },
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'pscad_restart_pscad',
|
|
282
|
+
description: '强制重启 PSCAD 自动化服务(卡死/10054 崩溃恢复):终止进程、重新启动并重连、重载当前工程。',
|
|
283
|
+
parameters: {},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: 'pscad_check_component_overlaps',
|
|
287
|
+
description: '布局自检:遍历画布全部组件检查锚点/端口重叠(build 前诊断)。',
|
|
288
|
+
parameters: {
|
|
289
|
+
min_gap: { type: 'integer', description: '最小间距(默认 2)' },
|
|
290
|
+
},
|
|
291
|
+
},
|
|
246
292
|
]
|
|
247
293
|
|
|
248
294
|
// ---------------- 插件主体 ----------------
|
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
|
@@ -68,7 +68,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
68
68
|
CONFIG_FILE_NAME: str = '.pscad_config.json' # 工作区本地配置文件
|
|
69
69
|
PSCAD_PORT: int = int(os.environ.get("PSCAD_PORT", "54329"))
|
|
70
70
|
DEFAULT_FOLDER: str = os.environ.get("PSCAD_WORK_DIR", "") # 无硬编码;空则回退当前工作目录
|
|
71
|
-
LAUNCH_WAIT_S: int =
|
|
71
|
+
LAUNCH_WAIT_S: int = 180 # PSCAD 冷启动(拉起→自动化端口就绪)实测可能 >60s,放宽到 180s
|
|
72
72
|
CONNECT_TIMEOUT_S: int = 20
|
|
73
73
|
|
|
74
74
|
# 配置缺失时的标准 Agent 返回(裸字典,供大模型识别并触发对话式引导)
|
|
@@ -228,15 +228,31 @@ def init_pscad_environment(exe_path: str, pylib_path: str = '') -> Dict[str, Any
|
|
|
228
228
|
"""
|
|
229
229
|
try:
|
|
230
230
|
if not pylib_path:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
# ① site-packages 已有 pip 版 mhi-pscad(Python 3.12+ 机器常见):
|
|
232
|
+
# 直接使用(空 pylib 表示不记录路径,运行时优先直导),无需发现
|
|
233
|
+
ok_sp, _ = _trial_import_mhi('')
|
|
234
|
+
if ok_sp:
|
|
235
|
+
pylib_path = ''
|
|
234
236
|
else:
|
|
237
|
+
discovered = _discover_pylib(exe_path)
|
|
238
|
+
if discovered:
|
|
239
|
+
pylib_path = discovered
|
|
240
|
+
else:
|
|
241
|
+
return {'error': 'ConfigMissing',
|
|
242
|
+
'instruction': ('未能在 PSCAD 安装目录自动发现 mhi.pscad,'
|
|
243
|
+
'site-packages 也无可用版本。请确认 PSCAD 5.x '
|
|
244
|
+
'安装完整(应有 bin/win64/mhi.zip),或 '
|
|
245
|
+
'pip install mhi-pscad 后重试,或手动提供 '
|
|
246
|
+
'mhi.pscad 库路径后再调用 init_pscad_environment。')}
|
|
247
|
+
# 保存前试导入验证:防止自动发现/指定路径在当前 Python 下不可导入
|
|
248
|
+
# (如 mhi.zip 2.2.1 依赖 Python 3.12 已移除的 distutils)时,把原本
|
|
249
|
+
# 可用的配置覆盖成坏配置。导入失败一律不保存,返回引导。
|
|
250
|
+
if pylib_path:
|
|
251
|
+
ok_imp, imp_msg = _trial_import_mhi(pylib_path)
|
|
252
|
+
if not ok_imp:
|
|
235
253
|
return {'error': 'ConfigMissing',
|
|
236
|
-
'instruction': ('
|
|
237
|
-
|
|
238
|
-
'bin/win64/mhi.zip),或手动提供 mhi.pscad '
|
|
239
|
-
'库路径后再调用 init_pscad_environment。')}
|
|
254
|
+
'instruction': ('mhi.pscad 无法在当前 Python 下导入,配置未保存:%s'
|
|
255
|
+
% imp_msg)}
|
|
240
256
|
r = save_config(exe_path, pylib_path)
|
|
241
257
|
if r['status'] == 'success':
|
|
242
258
|
r['data']['message'] = ('PSCAD 环境配置已保存。后续连接将自动使用该配置,'
|
|
@@ -246,6 +262,20 @@ def init_pscad_environment(exe_path: str, pylib_path: str = '') -> Dict[str, Any
|
|
|
246
262
|
return _err(exc)
|
|
247
263
|
|
|
248
264
|
|
|
265
|
+
def _trial_import_mhi(pylib: str) -> Tuple[bool, str]:
|
|
266
|
+
"""试导入验证:确认 pylib(目录或 zip;空串=仅 site-packages 直导)可导入 mhi.pscad。
|
|
267
|
+
|
|
268
|
+
不抛异常;失败返回带指引的消息(Python ≥3.12 的 distutils 问题等)。
|
|
269
|
+
"""
|
|
270
|
+
try:
|
|
271
|
+
if pylib and pylib not in sys.path:
|
|
272
|
+
sys.path.insert(0, pylib)
|
|
273
|
+
import mhi.pscad # noqa: F401
|
|
274
|
+
return True, ''
|
|
275
|
+
except Exception as exc:
|
|
276
|
+
return False, _mhi_import_error(exc, pylib or '<site-packages>')
|
|
277
|
+
|
|
278
|
+
|
|
249
279
|
def _cfg_exe() -> Optional[str]:
|
|
250
280
|
"""解析 PSCAD 可执行文件路径:本地配置优先,环境变量降级回退。"""
|
|
251
281
|
cfg = load_config()
|
|
@@ -352,26 +382,49 @@ def _require_config() -> Optional[Dict[str, Any]]:
|
|
|
352
382
|
return CONFIG_MISSING_RESPONSE
|
|
353
383
|
|
|
354
384
|
|
|
385
|
+
def _mhi_import_error(exc: Exception, pylib: str) -> str:
|
|
386
|
+
"""构造 mhi 导入失败消息;针对 Python ≥3.12 + distutils 给出明确降级指引。"""
|
|
387
|
+
ver = '%d.%d' % sys.version_info[:2]
|
|
388
|
+
hint = ''
|
|
389
|
+
if sys.version_info >= (3, 12) and 'distutils' in str(exc):
|
|
390
|
+
hint = ('。PSCAD 自带 mhi.zip 为 mhi.pscad 2.2.1,依赖 Python 3.12 已移除的 '
|
|
391
|
+
'distutils,仅支持 Python ≤3.11:请改用 ≤3.11 的 Python 作为桥接解释器,'
|
|
392
|
+
'或执行 pip install mhi-pscad 安装新版(无此依赖)')
|
|
393
|
+
return ('import mhi.pscad 失败(Python %s,pylib=%s): %s%s'
|
|
394
|
+
% (ver, pylib, exc, hint))
|
|
395
|
+
|
|
396
|
+
|
|
355
397
|
def _import_mhi() -> Tuple[bool, str]:
|
|
356
|
-
"""懒加载 mhi.pscad
|
|
398
|
+
"""懒加载 mhi.pscad:多候选导入,**绝不抛异常**。
|
|
357
399
|
|
|
358
|
-
|
|
359
|
-
|
|
400
|
+
顺序:① 已加载则直接绑定返回;② site-packages 直接导入(pip 安装的
|
|
401
|
+
mhi-pscad 新版天然可导入);③ 将配置/自动发现的 pylib(目录或 mhi.zip)
|
|
402
|
+
加入 sys.path 后导入。全部失败返回带指引的消息(Python ≥3.12 时提示
|
|
403
|
+
distutils 兼容问题与降级方案)。
|
|
360
404
|
"""
|
|
361
405
|
global mhi # 必须绑定到模块级:连接代码(_ensure_connected/_launch_pscad)
|
|
362
406
|
# 依赖 mhi.pscad.connect(...),函数内 import 只会绑定局部名导致 NameError
|
|
363
|
-
pylib = _cfg_pylib()
|
|
364
|
-
if not pylib:
|
|
365
|
-
return False, MISSING_CONFIG_MSG
|
|
366
407
|
try:
|
|
367
|
-
if 'mhi.pscad'
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
408
|
+
if 'mhi.pscad' in sys.modules:
|
|
409
|
+
import mhi.pscad # noqa: F401 幂等绑定模块级名字
|
|
410
|
+
return True, ''
|
|
411
|
+
# ② 先试 site-packages / 已有 sys.path(pip 版 mhi-pscad)
|
|
412
|
+
try:
|
|
413
|
+
import mhi.pscad # noqa: F401
|
|
414
|
+
return True, ''
|
|
415
|
+
except Exception:
|
|
416
|
+
pass
|
|
417
|
+
# ③ 候选 pylib(配置优先,_cfg_pylib 内部已含自动发现回退)
|
|
418
|
+
pylib = _cfg_pylib()
|
|
419
|
+
if not pylib:
|
|
420
|
+
return False, MISSING_CONFIG_MSG
|
|
421
|
+
if pylib not in sys.path:
|
|
422
|
+
sys.path.insert(0, pylib)
|
|
423
|
+
import mhi.pscad # noqa: F401
|
|
371
424
|
return True, ''
|
|
372
425
|
except Exception as exc:
|
|
373
|
-
|
|
374
|
-
|
|
426
|
+
pylib = _cfg_pylib() or ''
|
|
427
|
+
return False, _mhi_import_error(exc, pylib)
|
|
375
428
|
|
|
376
429
|
|
|
377
430
|
# ---------------------------------------------------------------------------
|
|
@@ -525,6 +578,12 @@ def connect_and_load_project(project_name: str,
|
|
|
525
578
|
try:
|
|
526
579
|
_pscad.create_case(project_name, folder=folder)
|
|
527
580
|
prj = _pscad.project(project_name)
|
|
581
|
+
# mhi 2.9.7 的 create_case 可能只在 PSCAD 会话内存建工程、不落盘
|
|
582
|
+
# (与 2.2.1 语义差异);显式 save() 保证 folder 下生成 .pscx
|
|
583
|
+
try:
|
|
584
|
+
prj.save()
|
|
585
|
+
except Exception:
|
|
586
|
+
pass
|
|
528
587
|
except Exception as exc:
|
|
529
588
|
return _err('create_case 失败: %s' % exc)
|
|
530
589
|
|