| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767 |
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width,initial-scale=1">
- <title>变桨液压回路仿真台 · SWT-4.0-130</title>
- <meta name="google" content="notranslate">
- <style>
- :root{
- --bg:#F4F6F8; --panel:#FFFFFF; --panel2:#EDF0F3; --ink:#10151B; --sub:#5A6472;
- --line:#D8DDE3; --grid:#E4E8EC;
- --hp:#C8302B; --lp:#1E7FC2; --gas:#D99A2B; --ok:#2E8B57; --warn:#B7791F;
- --chipbg:#EDF0F3; --focus:#1E7FC2;
- }
- @media (prefers-color-scheme: dark){:root{
- --bg:#0D1117; --panel:#151B23; --panel2:#1C242E; --ink:#E8EBEF; --sub:#93A0AD;
- --line:#2A3340; --grid:#222B36;
- --hp:#E05A50; --lp:#4FA3DC; --gas:#E2AC4A; --ok:#4CAF7D; --warn:#D9A24A;
- --chipbg:#1C242E;
- }}
- :root[data-theme="dark"]{
- --bg:#0D1117; --panel:#151B23; --panel2:#1C242E; --ink:#E8EBEF; --sub:#93A0AD;
- --line:#2A3340; --grid:#222B36;
- --hp:#E05A50; --lp:#4FA3DC; --gas:#E2AC4A; --ok:#4CAF7D; --warn:#D9A24A;
- --chipbg:#1C242E;
- }
- :root[data-theme="light"]{
- --bg:#F4F6F8; --panel:#FFFFFF; --panel2:#EDF0F3; --ink:#10151B; --sub:#5A6472;
- --line:#D8DDE3; --grid:#E4E8EC;
- --hp:#C8302B; --lp:#1E7FC2; --gas:#D99A2B; --ok:#2E8B57; --warn:#B7791F;
- --chipbg:#EDF0F3;
- }
- *{box-sizing:border-box}
- body{margin:0;background:var(--bg);color:var(--ink);
- font:14px/1.55 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif;}
- .mono{font-family:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;
- font-variant-numeric:tabular-nums;}
- header{padding:14px 18px 10px;border-bottom:1px solid var(--line)}
- header h1{margin:0;font-size:17px;font-weight:700;letter-spacing:.02em}
- header .sub{color:var(--sub);font-size:12px;margin-top:2px}
- .bar{display:flex;flex-wrap:wrap;gap:8px 14px;align-items:center;padding:10px 18px;
- border-bottom:1px solid var(--line);background:var(--panel)}
- .bar .grp{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
- .bar .lab{font-size:11px;color:var(--sub);letter-spacing:.08em;text-transform:uppercase}
- button{font:inherit;color:var(--ink);background:var(--chipbg);border:1px solid var(--line);
- border-radius:5px;padding:5px 12px;cursor:pointer}
- button:hover{border-color:var(--sub)}
- button:focus-visible{outline:2px solid var(--focus);outline-offset:1px}
- button.on{background:var(--ink);color:var(--bg);border-color:var(--ink)}
- button.estop{border-color:var(--hp);color:var(--hp);font-weight:700}
- button.estop.on{background:var(--hp);color:#fff}
- main{display:grid;grid-template-columns:305px minmax(430px,1fr) 355px;gap:12px;
- padding:12px 18px;align-items:start}
- .card{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:12px 14px}
- .card h3{margin:0 0 8px;font-size:12px;letter-spacing:.08em;color:var(--sub);
- text-transform:uppercase;font-weight:600}
- .fault{margin-bottom:14px}
- .fault .name{display:flex;justify-content:space-between;align-items:baseline;font-weight:600;font-size:13px}
- .fault .val{color:var(--hp)}
- .fault input[type=range]{width:100%;accent-color:var(--hp)}
- .fault .hint{font-size:11.5px;color:var(--sub);margin-top:1px}
- .presets{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}
- .presets button{font-size:12px;padding:3px 9px}
- table.mx{width:100%;border-collapse:collapse;font-size:12.5px}
- table.mx th,table.mx td{padding:3.5px 4px;text-align:right;border-bottom:1px solid var(--grid)}
- table.mx th:first-child,table.mx td:first-child{text-align:left}
- table.mx thead th{color:var(--sub);font-weight:600;font-size:11px}
- canvas{display:block;width:100%;border:1px solid var(--line);border-radius:8px;background:var(--panel)}
- .split{display:grid;grid-template-columns:290px 1fr;gap:12px;margin-top:12px}
- .verdict{margin-top:10px;padding:9px 12px;border-radius:6px;font-size:13px;display:none}
- .verdict.ok{display:block;background:rgba(46,139,87,.12);border:1px solid var(--ok)}
- .verdict.bad{display:block;background:rgba(200,48,43,.12);border:1px solid var(--hp)}
- .chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
- .chip{font-size:11.5px;background:var(--chipbg);border:1px solid var(--line);
- border-radius:4px;padding:3px 8px;color:var(--sub)}
- footer{padding:6px 18px 26px;color:var(--sub);font-size:12px;max-width:1100px}
- footer b{color:var(--ink)}
- label.tg{display:inline-flex;gap:5px;align-items:center;cursor:pointer;font-size:12.5px}
- input[type=checkbox]{accent-color:var(--hp)}
- @media (max-width:1240px){main{grid-template-columns:290px 1fr}.right{grid-column:1/-1;
- display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:12px}}
- @media (max-width:860px){main{grid-template-columns:1fr}.split{grid-template-columns:1fr}}
- @media (prefers-reduced-motion: reduce){.anim{display:none}}
- </style><style id="guanlan-pitch-light">
- :root,:root[data-theme="dark"],:root[data-theme="light"]{color-scheme:light;--bg:#f2f7fa;--panel:#fff;--panel2:#eaf3f9;--ink:#19374b;--sub:#526d80;--line:#ccdee9;--grid:#e1ebf2;--chipbg:#edf5fa;--focus:#216b8a;--hp:#C8302B;--lp:#1E7FC2;--gas:#D99A2B;--ok:#2E8B57;--warn:#B7791F}
- body{background:var(--bg);color:var(--ink)}header{padding:24px 26px;background:linear-gradient(115deg,#e4f1f9,#f3f9fc);border-bottom:1px solid #c8deeb}header h1{font-size:22px;line-height:1.5;color:#16455f}header .sub{max-width:1150px;line-height:1.8;margin-top:8px}.bar{padding:14px 24px;gap:12px 20px;background:#fff}button{border-radius:8px;padding:7px 12px}button.on:not(.estop){background:#216b8a;border-color:#216b8a;color:#fff}main{padding:20px 24px;gap:18px}.card{border-radius:12px;padding:18px;box-shadow:0 3px 14px #214e6c08}.card h3{color:#216b8a;background:#edf5fa;border-left:3px solid #469ab8;padding:9px 11px;border-radius:4px;letter-spacing:.03em;font-size:13px}.fault{margin-bottom:18px}.fault .hint{line-height:1.7}canvas{border-radius:10px}table.mx th,table.mx td{padding:7px 5px}table.mx thead{background:#edf5fa}.chip{border-radius:6px;padding:5px 8px}footer{padding:12px 24px 28px}@media(max-width:600px){main{padding:12px;gap:12px}header{padding:18px}header h1{font-size:18px}.bar{padding:12px}.right{grid-template-columns:minmax(0,1fr)}}
- </style>
- <style id="pitch-visual-banner">header{position:relative;isolation:isolate;overflow:hidden;background:linear-gradient(110deg,#0c2e40,#124c5d);border-color:#326274;padding:26px}header:after{content:"";position:absolute;z-index:-1;right:0;top:0;width:40%;height:100%;opacity:.32;background-image:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20520%20320%22%3E%3Cdefs%3E%3Cpattern%20id%3D%22g%22%20width%3D%2226%22%20height%3D%2226%22%20patternUnits%3D%22userSpaceOnUse%22%3E%3Cpath%20d%3D%22M26%200H0V26%22%20fill%3D%22none%22%20stroke%3D%22%2372b8c3%22%20stroke-opacity%3D%22.1%22%2F%3E%3C%2Fpattern%3E%3C%2Fdefs%3E%3Cpath%20fill%3D%22url%28%23g%29%22%20d%3D%22M0%200h520v320H0z%22%2F%3E%3Cg%20fill%3D%22none%22%20stroke%3D%22%2367bbb9%22%20stroke-width%3D%221.7%22%20opacity%3D%22.65%22%3E%3Cpath%20d%3D%22M22%20262Q142%20224%20272%20257T505%20245M22%20276Q142%20238%20272%20271T505%20259M22%20290Q142%20252%20272%20285T505%20273%22%2F%3E%3Cpath%20d%3D%22M152%20253V131m0%200%204-81%206%202-5%2074%2061%2039-4%205-66-36-58%2046-4-5%2060-49M291%20246V98m0%200%204-79%206%202-5%2072%2061%2039-4%205-66-36-58%2046-4-5%2060-49M405%20264V158m0%200%203-62%205%202-4%2057%2047%2030-3%204-52-29-45%2036-3-4%2048-36%22%2F%3E%3Ccircle%20cx%3D%22152%22%20cy%3D%22131%22%20r%3D%224%22%20fill%3D%22%2367bbb9%22%2F%3E%3Ccircle%20cx%3D%22291%22%20cy%3D%2298%22%20r%3D%224%22%20fill%3D%22%2367bbb9%22%2F%3E%3Ccircle%20cx%3D%22405%22%20cy%3D%22158%22%20r%3D%224%22%20fill%3D%22%2367bbb9%22%2F%3E%3Cpath%20d%3D%22M35%20248v-53h63v50m-68-51%208-9h60l7%209zm16%202v48m17-48v45m17-45v46M40%20172h39m-21-12h41%22%2F%3E%3Cpath%20stroke-dasharray%3D%224%206%22%20d%3D%22M78%20250Q240%20190%20452%20238%22%2F%3E%3C%2Fg%3E%3Ccircle%20cx%3D%22465%22%20cy%3D%2228%22%20r%3D%2273%22%20fill%3D%22none%22%20stroke%3D%22%238fcbb5%22%20stroke-opacity%3D%22.12%22%2F%3E%3Ccircle%20cx%3D%22465%22%20cy%3D%2228%22%20r%3D%2299%22%20fill%3D%22none%22%20stroke%3D%22%238fcbb5%22%20stroke-opacity%3D%22.06%22%20stroke-width%3D%2220%22%2F%3E%3C%2Fsvg%3E");background-size:contain;background-repeat:no-repeat;background-position:right center}header h1{color:#fff}header h1 span{color:#bfdce6!important}header .sub{color:#bfdce6;font-size:12px;line-height:1.8}.card{border-top:3px solid #75aebd}.card h3{background:#eaf3f7;border-left-color:#1f8296}@media(max-width:600px){header{padding:18px}header:after{width:100%;opacity:.13}}</style><header>
- <h1>变桨液压回路仿真台 <span class="mono" style="font-weight:400;color:var(--sub)">SWT-4.0-130 · 图纸 1034384/1034386</span></h1>
- <div class="sub">泵持续运转 + 25 号阀滞环卸荷 + <b>双蓄能器</b>(泵站 70 号 10L/180bar 近端缓冲 · 轮毂 106 6×30L/125bar 顺桨主源)—— 参数标定自本场 38 台实测与原厂图纸;分别注入失效,看两者的签名有何不同</div>
- </header>
- <div class="bar">
- <div class="grp"><span class="lab">工况</span>
- <button id="m-reg" class="on">运行调节</button>
- <button id="m-close">收桨 0→82°</button>
- <button id="m-open">开桨 82→0°</button>
- <button id="m-estop" class="estop">紧急顺桨</button>
- </div>
- <div class="grp"><span class="lab">电源</span>
- <button id="pw" class="on">有电</button>
- </div>
- <div class="grp"><span class="lab">倍速</span>
- <input id="spd" type="range" min="1" max="40" value="10" style="width:110px">
- <span id="spdv" class="mono" style="font-size:12px">10×</span>
- </div>
- <div class="grp"><span class="lab">预设</span>
- <div class="presets">
- <button data-p="healthy">健康台</button>
- <button data-p="early">70号早期失氮</button>
- <button data-p="collapse">70号失效=复现 8#</button>
- <button data-p="hubfail">106失氮(假设)</button>
- <button data-p="bothfail">双失效(最不利)</button>
- <button data-p="leak">复现 25# 泄漏</button>
- <button data-p="pumpint">复现 14# 泵间歇</button>
- <button data-p="stuck">70号卡滞(假设)</button>
- </div>
- </div>
- </div>
- <main>
- <div>
- <div class="card">
- <h3>失效注入</h3>
- <div class="fault">
- <div class="name"><span>① 泵站70号失氮 <span style="color:var(--sub);font-weight:400">10L/180bar</span></span>
- <span class="val mono" id="v-p070">180 bar</span></div>
- <input type="range" id="s-p070" min="0" max="180" value="180" step="1">
- <div class="hint">180=铭牌 · ≈0–2 匹配 8# 实测(带宽135/周期2.6s) · A 类失效部位(现场证实, 4# 换件先例)</div>
- <label class="tg" style="margin-top:4px"><input type="checkbox" id="s-stuck">
- 70号活塞卡滞 <span style="color:var(--sub)">— 候选机理(未证实),油气零交换</span></label>
- </div>
- <div class="fault">
- <div class="name"><span>② 轮毂106失氮 <span style="color:var(--sub);font-weight:400">6×30L/125bar</span></span>
- <span class="val mono" id="v-p0H">125 bar</span></div>
- <input type="range" id="s-p0H" min="0" max="160" value="125" step="1">
- <div class="hint">125=铭牌@20℃ · 2026-08 现场气侧实测≈140(夏季温修内,正常) · 拖低=假设情形: 循环仅中度变化、失电顺桨失败</div>
- </div>
- <div class="fault">
- <div class="name"><span>③ 系统泄漏</span><span class="val mono" id="v-leak">1.0×</span></div>
- <input type="range" id="s-leak" min="10" max="60" value="10" step="1">
- <div class="hint">泄漏基线 2.2 L/min(+调节变桨≈1.0=实测总消耗3.2)· 25# ≈ 2.8×</div>
- </div>
- <div class="fault">
- <div class="name"><span>④ 泵性能</span><span class="val mono" id="v-pump">100%</span></div>
- <input type="range" id="s-pump" min="20" max="100" value="100" step="1">
- <div class="hint">磨损/容积效率下降 → 加载时间变长</div>
- <div class="name" style="margin-top:6px"><span> 间歇失供</span><span class="val mono" id="v-int">关</span></div>
- <input type="range" id="s-int" min="0" max="100" value="0" step="1">
- <div class="hint">随机掉供数十秒—数分钟 → 复现 14#/32# 的分钟级低压塌陷</div>
- </div>
- <div class="verdict" id="verdict"></div>
- </div>
- <div class="card" style="margin-top:12px">
- <h3>SCADA 派生指标(滚动 10 min)</h3>
- <table class="mx mono">
- <thead><tr><th>指标</th><th>仿真</th><th>健康实测</th><th>8# 实测</th></tr></thead>
- <tbody id="mx"></tbody>
- </table>
- </div>
- </div>
- <div>
- <div id="circuit-mode-caption" style="padding:14px 18px;background:#eaf4f7;color:#163546;line-height:1.8;border:1px solid #cfe1e8;border-radius:10px 10px 0 0;overflow-wrap:anywhere"></div><canvas id="cv" height="520" data-aspect="0.5"></canvas>
- <div class="split">
- <canvas id="pv" height="330"></canvas>
- <div class="card">
- <h3>活塞与进油量 · 逐循环</h3>
- <table class="mx mono"><tbody id="pistab"></tbody></table>
- <div class="chips">
- <span class="chip mono">ΔV = C · Δp</span>
- <span class="chip mono">C = 管路 + C70 + 0.095·C106</span>
- <span class="chip mono">Cᵢ = Vᵢ·p₀ᵢ / p²</span>
- <span class="chip mono">周期 ≈ C·带宽 ÷ 消耗</span>
- </div>
- </div>
- </div>
- </div>
- <div class="right">
- <canvas id="c1" height="200"></canvas>
- <canvas id="c2" height="170"></canvas>
- <canvas id="c3" height="170"></canvas>
- </div>
- </main>
- <footer>
- <b>边界与简化(读图前请先知道):</b>
- ① 单节点压力模型 + 「棘轮」叠加显示;双蓄能器按失效部位改判(v47p,2026-08-13 现场证实)的定量闭合参数化:
- 泵站 70 号(10L/180bar,ø7.0 节流+20×2.5 管)直连泵压主管<b>全额参与</b>充能循环,
- 轮毂 106(6×30L/125bar)经长管+旋转接头+118 单向阀<b>弱耦合</b>(kH=0.095 标定,健康总 C 对齐实测 0.087 L/bar);
- 70 号垮塌 → 循环尖峰 → 棘轮抬升 → 118 恒闭 → 轮毂被踢出循环(C 塌到纯管路 ≈0.003,对齐 8# 实测 0.0037)——此连锁按参数化关系建模,非流网求解;
- ② <b>三种失效的签名差异(本台的教学核心)</b>:70 号失效 = SCADA 循环签名剧变(吞吐 0.5L/周期 2.6s/带宽 135)而失电顺桨不受影响(数据窗 4 次全场停电全部安全顺桨、零超速 = 实证);
- 106 失氮 = 循环仅中度变化(吞吐 ~2L,与个体基线难分辨)而失电顺桨失去能量源 —— SCADA 判据对它几乎盲,覆盖它的是机组自检容量检查(UP03284 组,从轮毂 235bar 起测);
- 双失效 = SCADA 上与单独 70 号失效不可分辨 + 顺桨失败。<b>两套监测互补,缺一即盲</b>;
- ③ 紧急顺桨用轮毂 106 设计容量 6×30L 与等温气体定律,缸驱动背压取 25 bar(气动力矩未建模)——「低预充仍勉强完成」是理想下界,实机以现场实测为准;70 号对顺桨的贡献(&lt;2L,约 7%)未建模;
- ④ 预设「复现 X# 签名」意为让派生指标匹配该台实测,其 p₀ 数值不是实测氮压;「106失氮」是<b>假设情形</b>——本场 106 已实测正常(2026-08 气侧 ≈140 bar)。
- 数据源:图纸 1034384/1034386 · 实机参数 UP07200/07228(245/195)· 38 台 SCADA 实测(2025-01→2026-07)· 2026-08-13 现场核实(70 号换件履历 + 106 气侧实测)。
- </footer>
- <script>
- 'use strict';
- /* ================= 物理内核(标定值,来源见页脚) ================= */
- const K = {
- Qp: 36.8/60, // 变桨泵 L/s(25.4ccm×1450rpm)
- pLoad: 194, pUnload: 246, pRelief: 280,
- dLoad: 0.80, dUnload: 0.14, // 加载/卸荷响应延迟 s(五台反推 0.80±0.03)
- V70: 10, p70n: 180, // 泵站 70 号手刹蓄能器 10 L / 180 bar(ø7.0 节流+20×2.5 管直连泵压主管)
- V0H: 180, p0Hn: 125, // 轮毂 106 蓄能器 6×30 L / 125 bar(旋转接头+118 之后,顺桨主源)
- kH: 0.095, // 轮毂对充能循环的弱耦合系数(标定: 健康总 C 对齐实测 0.087 L/bar)
- Cline: 0.0025, // 管路/油柔度残余容变 L/bar(对齐 8# 塌陷签名 0.50L/2.6s/带宽135)
- LpD: 0.2642, LpDb: 0.0881, // 总/单叶片 L/度(环形腔 ø125/90×611)
- qLeak0: 2.2/60, // 泄漏基线 L/s @220bar (总消耗3.2 = 此项 + 调节变桨≈1.0)
- driveP: 25, // 缸驱动背压假设 bar
- d121: 2.4, // 顺桨节流孔 mm(每叶片块一只)
- rho: 870, Cd: 0.62,
- T0: 38, TperKW: 3.2, Ttau: 240, // 油温:+4 K/kW,τ=240 s
- };
- const orifQ = (mm, dp) => dp<=0 ? 0 :
- K.Cd*Math.PI/4*(mm/1000)**2*Math.sqrt(2*dp*1e5/K.rho)*1000; // L/s
- /* ================= 状态 ================= */
- const S = {
- t:0, p:220, pH:238, th:2, mode:'reg', power:true,
- v25:false, v25cmd:false, delayT:0, // 阀 25(true=得电=加载)
- Vgas:0, // 顺桨期气腔体积(动态)
- estop:null, // {t0, done, stall...}
- oilT:38, thCmd:2, regPh:0,
- intState:0, intT:0, // 间歇失供状态机
- cyc:[], lastUnload:0, loadStart:null, lastVc:null, lastPer:null,
- buf:[], acc:0, ratchet:238,
- };
- const F = { p070:180, p0H:125, stuck:false, leak:1.0, pump:1.0, inter:0 };
- /* 单罐等温容变 L/bar(V=总容积, p0=预充;p≤p0 活塞顶死, 只剩止点软区) */
- function accC(V, p0, p){
- if (p0 < 0.5) return 0;
- if (p <= p0) return 0.02;
- return V*p0/(p*p);
- }
- /* 70号垮塌 → 尖峰 → 棘轮抬升 → 118 恒闭 → 轮毂被踢出循环(v47p 连锁, 参数化) */
- function hubGate(){
- return F.stuck ? 0 : Math.max(0, Math.min(1, (F.p070-20)/60));
- }
- /* 充能循环等效容变 = 管路 + 泵站70号(全额) + 轮毂106(弱耦合×连锁门) */
- function Ccharge(p){
- let c = K.Cline;
- if (!F.stuck) c += accC(K.V70, F.p070, p);
- c += K.kH * hubGate() * accC(K.V0H, F.p0H, p);
- return c;
- }
- /* 稳态气腔占比(显示用) */
- function gasFrac(p, p0){
- if (p0 < 0.5) return 0;
- if (p <= p0) return 1;
- return Math.min(1, p0/p);
- }
- /* ================= 仿真步 ================= */
- function step(dt){
- S.t += dt;
- const perEst = Math.max(1.6, S.lastPer ?? 90);
- const qCycle = 0.45/perEst; // 阀切换/瞬态每循环开销 ≈0.45 L (健康台 0.3 L/min, 塌陷台 ~10 L/min)
- const leak = K.qLeak0*F.leak*(S.p/220) + qCycle;
- /* 泵可用性 */
- let pumpOn = S.power && S.mode!=='estop';
- if (pumpOn && F.inter>0){ // 间歇失供
- S.intT -= dt;
- if (S.intState===0 && S.intT<=0){ S.intState=1; S.intT=15+F.inter*165*Math.random(); }
- else if (S.intState===1 && S.intT<=0){ S.intState=0; S.intT=(30+120*Math.random())/(0.3+F.inter); }
- if (S.intState===1) pumpOn=false;
- } else S.intState=0;
- /* 桨距指令 */
- let rate=0;
- if (S.mode==='reg'){
- S.regPh+=dt;
- S.thCmd = 3.2+2.4*Math.sin(2*Math.PI*S.regPh/300)+0.9*Math.sin(2*Math.PI*S.regPh/67);
- rate = Math.max(-3,Math.min(3,(S.thCmd-S.th)/2.5)); // 实测量级: 平均|rate|≈0.08°/s ⇒ 变桨耗油≈1.2 L/min
- } else if (S.mode==='close'){ rate = S.th<82?3:0; }
- else if (S.mode==='open'){ rate = S.th>0?-3:0; }
- if (S.mode!=='estop'){
- if (S.p < K.driveP+8) rate=0; // 无压则动不了
- S.th = Math.max(0,Math.min(82,S.th+rate*dt));
- }
- const qPitch = Math.abs(rate)*K.LpD; // L/s(进给由 HP 供)
- /* 阀 25 滞环 + 延迟 */
- if (S.mode==='estop' || !S.power){ S.v25cmd=false; }
- else if (!S.v25cmd && S.p<=K.pLoad){ S.v25cmd=true; S.delayT=K.dLoad; }
- else if ( S.v25cmd && S.p>=K.pUnload){ S.v25cmd=false; S.delayT=K.dUnload; }
- if (S.delayT>0){ S.delayT-=dt; }
- else if (S.v25 !== S.v25cmd){
- S.v25 = S.v25cmd;
- if (S.v25){ S.loadStart=S.t; }
- else if (S.loadStart!=null){ // 一个循环完成
- const tl=S.t-S.loadStart, per=S.t-S.lastUnload;
- S.lastVc=tl*K.Qp*F.pump; S.lastPer=per;
- S.cyc.push({t:S.t, tl, per}); S.lastUnload=S.t;
- if (S.cyc.length>4000) S.cyc.shift();
- }
- }
- /* 压力动力学(单节点) */
- const Qin = (S.v25&&pumpOn) ? K.Qp*F.pump : 0;
- const C = Ccharge(S.p);
- if (S.mode!=='estop'){
- S.p += (Qin - leak - qPitch)/C*dt;
- if (S.p>K.pRelief) S.p=K.pRelief;
- if (S.p<2) S.p=2;
- }
- /* 轮毂侧显示:健康跟随 vs 棘轮锁定, 按耦合健康度 h 连续混合 */
- if (S.mode!=='estop'){
- const h = Math.max(0, Math.min(1, (Ccharge(230)-0.006)/(0.055-0.006)));
- if (S.p >= S.ratchet) S.ratchet=S.p; // 118 单向: 峰值被锁
- else S.ratchet = Math.max(S.p, S.ratchet - (0.15 + qPitch/0.006)*dt);
- const pHealthy = S.v25 ? S.p-2 : Math.min(244, S.p+26); // 实测: 健康台叶片 p99=244, 中位=站侧+26
- S.pH = h*pHealthy + (1-h)*S.ratchet;
- }
- /* 紧急顺桨(全阀失电, 轮毂106→103→121→缸;70号不在此路, 其失效不碍顺桨) */
- if (S.mode==='estop' && S.estop && !S.estop.done){
- const e=S.estop;
- // 气腔压力: 等温 p·V=p0·V0. Vgas 随放油增大, 单调, 不会发散.
- const alive = (F.p0H>=0.5);
- let pAcc = alive ? (K.V0H*F.p0H)/Math.max(S.Vgas, 1) : Math.max(2, S.pH*Math.exp(-e.dV/8));
- if (!isFinite(pAcc)) pAcc=2;
- const dp = pAcc - K.driveP;
- const Q = (S.th<82 && dp>0.5) ? 3*orifQ(K.d121, dp) : 0;
- S.th = Math.min(82, S.th + (Q/K.LpD)*dt);
- if (alive) S.Vgas = Math.min(K.V0H, S.Vgas + Q*dt);
- e.dV += Q*dt; e.pMin=Math.min(e.pMin,pAcc); S.pH=pAcc; S.p=Math.max(4,S.p-8*dt);
- e.stallT = (Q<=0.002) ? (e.stallT||0)+dt : 0;
- if (S.th>=81.9){ e.done=true; e.ok=true; e.tEnd=S.t; showVerdict(); }
- else if (e.stallT>2.5){ e.done=true; e.ok=false; e.tEnd=S.t; showVerdict(); }
- else if (S.t-e.t0>180){ e.done=true; e.ok=false; e.tEnd=S.t; showVerdict(); } // 硬超时兜底
- }
- /* NaN 断路器: 任何一步失稳立即复位, 防止渲染层死循环 */
- if (!isFinite(S.p)||!isFinite(S.pH)||!isFinite(S.th)){
- S.p=220; S.pH=238; S.th=Math.max(0,Math.min(82,S.th||2));
- S.ratchet=238; S.Vgas=0; if(S.estop){S.estop.done=true;S.estop.ok=false;}
- }
- /* 油温 (duty 每 0.5 s 才重算一次, 避免每步扫全 cyc 数组) */
- S.dutyAcc=(S.dutyAcc||0)+dt;
- if (S.dutyC==null || S.dutyAcc>=0.5){ S.dutyAcc=0; S.dutyC=dutyNow(); }
- const Pheat = S.dutyC*K.Qp*F.pump*220/10 + (F.leak-1)*0.5; // kW
- const Ttar = K.T0 + Math.max(0,(Pheat-1.2))*K.TperKW;
- S.oilT += (Ttar-S.oilT)/K.Ttau*dt;
- /* 采样 */
- S.acc+=dt;
- if (S.acc>=0.25){ S.acc=0;
- S.buf.push({t:S.t,p:S.p,pH:S.pH,th:S.th,v:S.v25?1:0});
- if (S.buf.length>4800) S.buf.shift();
- }
- }
- function dutyNow(){
- const w=S.cyc.filter(c=>c.t>S.t-600);
- if (!w.length) return S.v25?0.5:0.08;
- const span=Math.min(600, Math.max(60, S.t - (S.cyc[0]?.t ?? 0) + 60));
- return Math.min(0.95, w.reduce((a,c)=>a+c.tl,0)/span);
- }
- /* ================= 指标 ================= */
- const MEAS_H={vc:'4.9',per:'96',cnt:'6',band:'50',duty:'7.5',oil:'38',ov:'0.0'};
- const MEAS_8={vc:'0.50',per:'2.6',cnt:'228',band:'135',duty:'32.3',oil:'51',ov:'98.3'};
- function metrics(){
- const w=S.cyc.filter(c=>c.t>S.t-600);
- const pb=S.buf.filter(b=>b.t>S.t-600);
- const band = pb.length? (Math.max(...pb.map(b=>b.p))-Math.min(...pb.map(b=>b.p))) : 0;
- const ov = pb.length? pb.filter(b=>b.pH>246).length/pb.length*100 : 0;
- const cnt = w.length? Math.round(w.length*600/Math.min(600,Math.max(60,S.t))) : 0;
- const vc = S.lastVc!=null? S.lastVc.toFixed(2):'—';
- const per= S.lastPer!=null? S.lastPer.toFixed(1):'—';
- return [
- ['每次进油量 L', vc, MEAS_H.vc, MEAS_8.vc],
- ['充能周期 s', per, MEAS_H.per, MEAS_8.per],
- ['加载次数 /10min', cnt||'—', MEAS_H.cnt, MEAS_8.cnt],
- ['压力带宽 bar', band?band.toFixed(0):'—', MEAS_H.band, MEAS_8.band],
- ['加载占空比 %', (dutyNow()*100).toFixed(1), MEAS_H.duty, MEAS_8.duty],
- ['液压油温 ℃', S.oilT.toFixed(1), MEAS_H.oil, MEAS_8.oil],
- ['叶片侧>246 %', ov.toFixed(1), MEAS_H.ov, MEAS_8.ov],
- ];
- }
- function pistonRows(){
- const inEstop = S.mode==='estop' && F.p0H>=0.5 && S.Vgas>0;
- const Vg70 = K.V70*gasFrac(S.p, F.stuck?0:F.p070);
- const VgH = inEstop ? S.Vgas : K.V0H*gasFrac(S.pH, F.p0H);
- const pg70 = (F.p070<0.5||F.stuck)? '—' : (S.p>F.p070? S.p.toFixed(0):F.p070.toFixed(0));
- const pgH = (F.p0H<0.5)? '—' : (inEstop? (K.V0H*F.p0H/Math.max(VgH,1e-6)).toFixed(0)
- : (S.pH>F.p0H? S.pH.toFixed(0):F.p0H.toFixed(0)));
- return [
- ['70号 预充 / 气压', (F.stuck?'卡滞':F.p070.toFixed(0))+' / '+pg70+' bar'],
- ['70号 储油(10 L 罐)', (K.V70-Vg70).toFixed(1)+' L'],
- ['106 预充 / 气压', F.p0H.toFixed(0)+' / '+pgH+' bar'],
- ['106 储油(6 只合计)', (K.V0H-VgH).toFixed(1)+' L'],
- ['等效容变 C(充能)', Ccharge(S.p).toFixed(4)+' L/bar'],
- ['本循环吞吐 ΔV', S.lastVc!=null? S.lastVc.toFixed(2)+' L':'—'],
- ['顺桨需求(压超速/全程)', '5.3 / 21.7 L'],
- ];
- }
- /* ================= 绘制 ================= */
- const css=v=>getComputedStyle(document.documentElement).getPropertyValue(v).trim();
- function fit(cv){ const d=window.devicePixelRatio||1, w=cv.clientWidth;
- if(!cv.dataset.h) cv.dataset.h=cv.getAttribute('height'); // 设计高度只记一次
- const asp=+cv.dataset.aspect||0; // aspect: 高=宽×aspect(回路图铺满不留白)
- /* 迟滞死区+限频: 宽度变化>16px 且距上次重设>500ms 才重设画布 —
- 每帧重设会与滚动条出没形成布局振荡, Chrome 主线程被打死(2026-08-21 实测根因);
- Safari 滚动条不占宽故无感。绘制端使用画布实际尺寸, CSS 拉伸差≤16px 无感。 */
- const lw=+(cv.dataset.lw||0), lt=+(cv.dataset.lt||0), tn=performance.now();
- if (w>0 && (!lw || (Math.abs(w-lw)>16 && tn-lt>500))){
- const h=asp? Math.round(w*asp) : +cv.dataset.h;
- if (cv.width!==Math.round(w*d)||cv.height!==Math.round(h*d)){
- cv.width=Math.round(w*d); cv.height=Math.round(h*d); cv.style.height=h+'px'; }
- cv.dataset.lw=w; cv.dataset.lt=tn;
- }
- const g=cv.getContext('2d'); g.setTransform(d,0,0,d,0,0);
- return [g, cv.width/d, cv.height/d]; }
- /* Safari<16.4 无 canvas roundRect -> 手绘圆角 */
- function rr(g,x,y,w,h,r){ g.beginPath(); g.moveTo(x+r,y);
- g.arcTo(x+w,y,x+w,y+h,r); g.arcTo(x+w,y+h,x,y+h,r);
- g.arcTo(x,y+h,x,y,r); g.arcTo(x,y,x+w,y,r); g.closePath(); }
- /* Safari canvas fillStyle 不认 color-mix -> JS 混色 */
- function hex2rgb(h){ h=h.trim(); if(h[0]==='#'){const v=parseInt(h.slice(1),16);
- return [(v>>16)&255,(v>>8)&255,v&255];} const m=h.match(/\d+/g)||[240,240,240];
- return [+m[0],+m[1],+m[2]]; }
- function mix(a,b,t){ const A=hex2rgb(a),B=hex2rgb(b);
- return 'rgb('+Math.round(A[0]*t+B[0]*(1-t))+','+Math.round(A[1]*t+B[1]*(1-t))+','+Math.round(A[2]*t+B[2]*(1-t))+')'; }
- let dashOff=0;
- function pcol(p){ // 压力→红色深浅 (JS 混色, 兼容 Safari canvas)
- const a=Math.max(.12,Math.min(1,p/260));
- return mix(css('--hp'), css('--panel2'), a);
- }
- function seg(g,pts,color,wd,flow){
- g.strokeStyle=color; g.lineWidth=wd; g.lineJoin='round'; g.beginPath();
- pts.forEach((p,i)=>i?g.lineTo(p[0],p[1]):g.moveTo(p[0],p[1])); g.stroke();
- if (flow>0.02 && !matchMedia('(prefers-reduced-motion: reduce)').matches){
- g.save(); g.strokeStyle=css('--bg'); g.lineWidth=Math.max(1.4,wd-2.4);
- g.setLineDash([3,9]); g.lineDashOffset=-dashOff*Math.min(6,flow*8);
- g.beginPath(); pts.forEach((p,i)=>i?g.lineTo(p[0],p[1]):g.moveTo(p[0],p[1]));
- g.stroke(); g.restore();
- }
- }
- function label(g,x,y,t,c,sz){ g.fillStyle=c||css('--sub');
- g.font=(sz||10.5)+'px ui-monospace,Menlo,monospace'; g.fillText(t,x,y); }
- function box(g,x,y,w,h,id){ g.strokeStyle=css('--sub'); g.lineWidth=1;
- g.strokeRect(x,y,w,h); if(id){ g.font='9.5px ui-monospace,monospace';
- g.fillStyle=css('--sub'); g.fillText(id,x,y-3);} }
- function drawCircuit(){
- const [g,W,H]=fit(cv); g.clearRect(0,0,W,H);
- const s=W/1040;
- g.save(); g.translate(0,(H-520*s)/2); g.scale(s,s);
- const HP=pcol(S.p), HPh=pcol(S.pH), LP=css('--lp'), ink=css('--ink'), sub=css('--sub');
- const Qflow=(S.v25&&S.power&&S.mode!=='estop'&&S.intState!==1)?1:0;
- const byp=(!S.v25&&S.power&&S.mode!=='estop'&&S.intState!==1)?1:0;
- const pitch=(S.mode!=='estop'&&Math.abs(S.th-(S.buf.length?S.buf[S.buf.length-1].th:S.th))>0)?0.4:
- (S.mode==='close'||S.mode==='open')?0.8:(S.mode==='reg'?0.3:0);
- const est=(S.mode==='estop'&&S.estop&&!S.estop.done)?1:0;
- /* ---- 油箱 & 泵 ---- */
- g.strokeStyle=sub; g.strokeRect(60,455,180,42);
- label(g,66,449,'油箱 23 · 400 L');
- // 电机+双泵
- g.beginPath(); g.arc(110,420,13,0,7); g.stroke(); label(g,104,424,'M',ink,11);
- g.beginPath(); g.arc(146,420,13,0,7); g.stroke();
- g.fillStyle=ink; g.beginPath(); g.moveTo(146,411); g.lineTo(152,424); g.lineTo(140,424); g.closePath(); g.fill();
- label(g,310,464,'25.4 ccm · 1450 rpm ⇒ 36.8 L/min','',9.5);
- label(g,310,446,S.power&&S.intState!==1?'泵连续运转':'泵停',S.power&&S.intState!==1?css('--ok'):css('--hp'),10.5);
- seg(g,[[146,433],[146,455]],LP,3,0.3); // 吸油
- seg(g,[[146,407],[146,352]],HP,4,Qflow||byp?1:0); // 泵出口
- /* ---- 25 号卸荷阀 ---- */
- seg(g,[[146,352],[240,352]],HP,3.5,byp);
- box(g,240,338,44,28,'25');
- g.fillStyle=S.v25?css('--hp'):css('--chipbg');
- g.fillRect(285,341,10,22);
- label(g,240,332,S.v25?'得电=封闭(加载)':'失电=泄回(卸荷)',S.v25?css('--hp'):sub,10);
- seg(g,[[284,366],[284,455]].map(p=>[p[0]-22,p[1]]),LP,3.5,byp); // 回油箱
- /* 12 单向阀 */
- seg(g,[[146,352],[146,318]],HP,4,Qflow);
- g.fillStyle=ink; g.beginPath(); g.moveTo(140,318); g.lineTo(152,318); g.lineTo(146,306); g.closePath(); g.fill();
- label(g,156,315,'12');
- /* 滤芯 11 */
- seg(g,[[146,306],[146,282]],HP,4,Qflow);
- g.save(); g.translate(146,270); g.rotate(Math.PI/4); g.strokeRect(-9,-9,18,18); g.restore();
- label(g,160,272,'11 β25µm');
- /* 溢流 10 */
- seg(g,[[146,258],[146,196]],HP,4,Qflow);
- seg(g,[[146,232],[210,232]],HP,2.5,S.p>=K.pRelief?1:0);
- box(g,210,222,26,20,'10'); label(g,240,236,'280 bar',sub);
- /* ---- 泵站 70 号手刹蓄能器 10 L(ø7.0 节流 + 20×2.5 管, 12 之后同段) ---- */
- const a7x=64,a7y=210,a7w=38,a7h=62, f70=gasFrac(S.p, F.stuck?0:F.p070);
- seg(g,[[a7x+a7w/2,a7y+a7h],[a7x+a7w/2,258],[146,258]],HP,3,0);
- g.strokeStyle=ink; g.lineWidth=1.6; g.strokeRect(a7x,a7y,a7w,a7h);
- const g7=Math.max(2,(a7h-6)*Math.min(1,f70));
- g.fillStyle=css('--gas'); g.fillRect(a7x+1.5,a7y+1.5,a7w-3,g7);
- g.fillStyle=sub; g.fillRect(a7x+1.5,a7y+1.5+g7,a7w-3,4);
- g.fillStyle=css('--hp'); g.globalAlpha=.75;
- g.fillRect(a7x+1.5,a7y+5.5+g7,a7w-3,a7h-7-g7); g.globalAlpha=1;
- if (F.stuck){ g.strokeStyle=css('--hp'); g.beginPath();
- g.moveTo(a7x,a7y); g.lineTo(a7x+a7w,a7y+a7h); g.moveTo(a7x+a7w,a7y); g.lineTo(a7x,a7y+a7h); g.stroke(); }
- label(g,36,a7y-8,'70 · 10 L p₀='+(F.stuck?'卡滞':F.p070.toFixed(0)),ink,10);
- label(g,36,a7y+a7h+14,'近端主缓冲','',9);
- /* 传感器 17 */
- g.beginPath(); g.arc(180,196,15,0,7); g.strokeStyle=sub; g.stroke();
- const a0=Math.PI*.75+S.p/300*Math.PI*1.5;
- g.strokeStyle=ink; g.beginPath(); g.moveTo(180,196);
- g.lineTo(180+11*Math.cos(a0),196+11*Math.sin(a0)); g.stroke();
- label(g,166,177,'17',sub);
- label(g,150,166,S.p.toFixed(0)+' bar',ink,15);
- label(g,150,152,'泵站侧 (SCADA 液压压力)','',9.5);
- /* ---- P HUB → 旋转接头 → 118 ---- */
- seg(g,[[146,196],[146,120],[330,120]],HP,4,Qflow);
- g.strokeStyle=sub;
- g.beginPath(); g.arc(352,120,16,-1.2,1.2); g.stroke();
- g.beginPath(); g.arc(362,120,16,Math.PI-1.2,Math.PI+1.2); g.stroke();
- label(g,332,96,'旋转接头');
- seg(g,[[378,120],[430,120]],HP,4,Qflow);
- g.fillStyle=ink; g.beginPath(); g.moveTo(430,113); g.lineTo(430,127); g.lineTo(444,120); g.closePath(); g.fill();
- label(g,412,72,'118 单向',sub);
- label(g,390,87,'← 泵→轮毂 单向 · 棘轮之源','',9);
- /* ---- 轮毂总管 ---- */
- seg(g,[[444,120],[980,120]],HPh,4,Qflow||est?1:0);
- /* 传感器107 */
- g.beginPath(); g.arc(505,158,15,0,7); g.strokeStyle=sub; g.stroke();
- const a1=Math.PI*.75+S.pH/300*Math.PI*1.5;
- g.strokeStyle=ink; g.beginPath(); g.moveTo(505,158);
- g.lineTo(505+11*Math.cos(a1),158+11*Math.sin(a1)); g.stroke();
- seg(g,[[505,143],[505,120]],HPh,2.5,0);
- label(g,580,151,'107',sub);
- label(g,580,168,S.pH.toFixed(0)+' bar',ink,14);
- label(g,580,184,'叶片侧 (桨叶油压)','',9.5);
- /* ---- 蓄能器 106(合并示意) ---- */
- const ax=590,ay=28,aw=54,ah=86;
- seg(g,[[ax+aw/2,ay+ah],[ax+aw/2,120]],HPh,3.5,Qflow*0.7);
- g.strokeStyle=ink; g.lineWidth=1.6; g.strokeRect(ax,ay,aw,ah);
- const gf=gasFrac(S.mode==='estop'&&F.p0H>=0.5? K.V0H*F.p0H/Math.max(S.Vgas,1e-6):S.pH, F.p0H);
- const gh=Math.max(2,(ah-8)*Math.min(1,(S.mode==='estop'&&S.Vgas>0&&F.p0H>=0.5)?S.Vgas/K.V0H:gf));
- g.fillStyle=css('--gas'); g.fillRect(ax+1.5,ay+1.5,aw-3,gh);
- g.fillStyle=sub; g.fillRect(ax+1.5,ay+1.5+gh,aw-3,5); // 活塞
- g.fillStyle=css('--hp'); g.globalAlpha=.75;
- g.fillRect(ax+1.5,ay+6.5+gh,aw-3,ah-8-gh); g.globalAlpha=1;
- label(g,ax-4,ay-8,'106 · 6×30 L 合并示意 · 顺桨主源 · p₀='+F.p0H.toFixed(0),ink,10);
- /* ---- 比例阀 102 → 缸 ---- */
- seg(g,[[700,120],[700,206]],HPh,3.5,pitch);
- box(g,672,206,56,30,'102');
- const pv=S.mode==='estop'?'—':(rateDir()>0?'b 位':rateDir()<0?'a 位':'中位');
- label(g,676,226,'比例阀 '+pv,ink,10);
- seg(g,[[728,221],[790,221]],HPh,3,pitch);
- /* 缸 */
- g.strokeStyle=ink; g.strokeRect(790,204,120,34);
- const rod=790+18+(S.th/82)*76;
- g.fillStyle=sub; g.fillRect(790+2,206,rod-792,30);
- g.strokeStyle=ink; g.beginPath(); g.moveTo(rod,221); g.lineTo(rod+56,221); g.stroke();
- label(g,792,198,'124/125 · ø125/90×611 ×2缸×3叶',sub,9.5);
- /* 叶片角 */
- g.save(); g.translate(952,221); g.rotate(-S.th*Math.PI/180);
- g.fillStyle=css('--lp'); g.globalAlpha=.85;
- g.beginPath(); g.ellipse(0,0,34,9,0,0,7); g.fill(); g.globalAlpha=1; g.restore();
- label(g,928,262,'θ = '+S.th.toFixed(1)+'°',ink,14);
- /* ---- 紧急旁路 103→121 ---- */
- seg(g,[[560,120],[560,300],[770,300],[770,238]],est?HPh:css('--grid'),3, est);
- box(g,596,288,40,24,'103'); label(g,600,304,'失电=通',est?ink:sub,9.5);
- // 节流孔 121
- g.strokeStyle=est?ink:sub; g.beginPath();
- g.moveTo(690,292); g.lineTo(702,300); g.lineTo(690,308); g.moveTo(718,292); g.lineTo(706,300); g.lineTo(718,308); g.stroke();
- label(g,682,324,'121 ø2.4 顺桨限速',est?css('--hp'):sub,10);
- /* ---- 回油 ---- */
- seg(g,[[700,236],[700,398],[146,398]],LP,3.5,pitch||est?0.6:0.15);
- g.strokeStyle=sub; g.beginPath();
- g.moveTo(640,392); g.lineTo(650,398); g.lineTo(640,404); g.moveTo(664,392); g.lineTo(654,398); g.lineTo(664,404); g.stroke();
- label(g,628,416,'116/122 ø1.8 回油',sub,9.5);
- seg(g,[[146,398],[146,455]],LP,3.5,0.3);
- label(g,955,114,'P',sub); label(g,60,392,'T',sub);
- /* 模式标注 */
- const mtxt={reg:'运行调节:比例阀 102 微调;泵站 70 号=滞环循环的近端主缓冲,轮毂 106 经 118 弱参与',
- close:'收桨:102 换向 b 位,需 47 L/min > 泵 36.8 ⇒ 蓄能器补差额',
- open:'开桨:102 换向 a 位(116 同时得电)',
- estop:'紧急顺桨:全阀失电 → 轮毂 106 经 103→121 推缸(70 号不在此路,其失效不碍顺桨)'};
- document.getElementById('circuit-mode-caption').textContent=mtxt[S.mode];
- if (S.intState===1) document.getElementById('circuit-mode-caption').textContent+=' · ⚠ 泵间歇失供中…';
- g.restore(); dashOff+=1;
- }
- function rateDir(){ if(S.mode==='close')return 1; if(S.mode==='open')return -1;
- const d=S.thCmd-S.th; return Math.abs(d)>.1?Math.sign(d):0; }
- /* 活塞特写:双罐并排(左=泵站70号, 右=轮毂106单只) */
- function drawPiston(){
- const [g,W,H]=fit(pv); g.clearRect(0,0,W,H);
- const ink=css('--ink'), sub=css('--sub');
- label(g,14,18,'蓄能器剖面 · 泵站70号 vs 轮毂106',ink,12);
- const y=44, h=142, w=64;
- function tank(x, frac, ttl, cap, stuck){
- label(g,x-4,y-8,ttl,ink,10);
- g.strokeStyle=ink; g.lineWidth=2; rr(g,x,y,w,h,8); g.stroke();
- const gh=Math.max(3,(h-10)*Math.min(1,frac));
- g.save(); rr(g,x+2,y+2,w-4,h-4,6); g.clip();
- g.fillStyle=css('--gas'); g.fillRect(x,y,w,gh+3);
- for(let i=7;i<gh;i+=12){ g.strokeStyle='rgba(0,0,0,.10)'; g.beginPath();
- g.moveTo(x+5,y+i); g.lineTo(x+w-5,y+i+3); g.stroke(); }
- g.fillStyle=sub; g.fillRect(x,y+gh+1,w,8);
- g.fillStyle=css('--hp'); g.globalAlpha=.8; g.fillRect(x,y+gh+9,w,h-gh-9); g.globalAlpha=1;
- g.restore();
- if (stuck){ g.strokeStyle=css('--hp'); g.lineWidth=2.5;
- g.beginPath(); g.moveTo(x+6,y+gh-14); g.lineTo(x+w-6,y+gh+22);
- g.moveTo(x+w-6,y+gh-14); g.lineTo(x+6,y+gh+22); g.stroke(); }
- label(g,x-4,y+h+13,cap,sub,9);
- }
- tank(24, gasFrac(S.p, F.stuck?0:F.p070),
- '70号 10L/'+(F.stuck?'卡滞':F.p070.toFixed(0)), '泵压主管 · 近端缓冲', F.stuck);
- const inEstop=S.mode==='estop'&&F.p0H>=0.5&&S.Vgas>0;
- tank(122, inEstop? Math.min(1,S.Vgas/K.V0H) : gasFrac(S.pH,F.p0H),
- '106单只 30L/'+F.p0H.toFixed(0), '叶片块×6 · 顺桨主源', false);
- label(g,200,y+8,'黄=氮气',css('--gas'),9.5);
- label(g,200,y+22,'灰=活塞',sub,9.5);
- label(g,200,y+36,'红=油侧',css('--hp'),9.5);
- // 吞吐条 + 三态要点
- const by=y+h+30, bw=W-40;
- label(g,20,by,'本循环吞吐 ΔV '+(S.lastVc!=null?S.lastVc.toFixed(2):'—')+' L(健康 4.9 · 8# 0.50)',ink,11);
- g.strokeStyle=css('--grid'); g.strokeRect(20,by+7,bw,12);
- const v=Math.min(6,S.lastVc??0);
- g.fillStyle=css('--hp'); g.fillRect(20,by+7,bw*v/6,12);
- label(g,20,by+36,'70号失氮 → 循环签名剧变(快循环/宽带宽)· 顺桨不受影响',sub,9.5);
- label(g,20,by+52,'106失氮 → 循环仅中度变化 · 失电顺桨失去能量源',sub,9.5);
- label(g,20,by+68,'双失效 → SCADA 签名与单独70号失效不可分辨 + 顺桨失败',sub,9.5);
- label(g,20,by+84,'气体定律 p·V=p₀·V₀(等温)· 气少=硬弹簧 · 无气=钢块',sub,9.5);
- }
- /* 走线图 */
- function chart(cv,rows,ylo,yhi,fmt,refs,title){
- const [g,W,H]=fit(cv); g.clearRect(0,0,W,H);
- const L=44,R=10,T=22,B=18, ink=css('--ink'), sub=css('--sub');
- label(g,L,14,title,ink,11.5);
- const win=120, t1=S.t, t0=t1-win;
- const X=t=>L+(t-t0)/win*(W-L-R), Y=v=>T+(1-(v-ylo)/(yhi-ylo))*(H-T-B);
- g.strokeStyle=css('--grid');
- for(let i=0;i<=4;i++){ const v=ylo+(yhi-ylo)*i/4;
- g.beginPath(); g.moveTo(L,Y(v)); g.lineTo(W-R,Y(v)); g.stroke();
- label(g,4,Y(v)+3,fmt(v),sub,9.5); }
- (refs||[]).forEach(r=>{ g.strokeStyle=r[2]; g.setLineDash([5,4]);
- g.beginPath(); g.moveTo(L,Y(r[0])); g.lineTo(W-R,Y(r[0])); g.stroke(); g.setLineDash([]);
- label(g,W-R-58,Y(r[0])-3,r[1],r[2],9); });
- rows.forEach(rw=>{
- g.strokeStyle=rw.c; g.lineWidth=rw.w||1.6; g.beginPath(); let pen=false;
- for(const b of S.buf){ if(b.t<t0)continue; const v=rw.f(b);
- if(v==null){pen=false;continue;}
- const x=X(b.t),y=Y(Math.max(ylo,Math.min(yhi,v)));
- pen?g.lineTo(x,y):g.moveTo(x,y); pen=true; }
- g.stroke();
- if(S.buf.length){ const last=rw.f(S.buf[S.buf.length-1]);
- if(last!=null) label(g,W-R-46,Y(Math.max(ylo,Math.min(yhi,last)))-5,rw.n+' '+fmt(last),rw.c,9.5); }
- });
- }
- /* ================= 判定与 UI ================= */
- function showVerdict(){
- const e=S.estop, el=document.getElementById('verdict');
- if(!e||!e.done) return;
- const dt=(e.tEnd-e.t0).toFixed(1);
- if(e.ok){ el.className='verdict ok';
- const n70=(F.p070<90||F.stuck)? '当前 70 号已注入失效但顺桨仍完成 —— 顺桨主源是轮毂 106(与数据窗 4 次停电全部安全顺桨的实证一致)。'
- : '等温理想下界 —— 低预充“勉强完成”不代表实机安全,以现场实测为准。';
- el.innerHTML=`✓ 顺桨完成:82° 用时 <b class="mono">${dt} s</b> · 放油 <b class="mono">${e.dV.toFixed(1)} L</b> · 最低压 <b class="mono">${e.pMin.toFixed(0)} bar</b><br><span style="color:var(--sub);font-size:12px">${n70}</span>`;
- } else { el.className='verdict bad';
- el.innerHTML=`✗ 顺桨失败:卡滞在 <b class="mono">${S.th.toFixed(0)}°</b>(用时 ${dt} s)· 已放 ${e.dV.toFixed(1)} L · 压力 ${e.pMin.toFixed(0)} bar &lt; 驱动需求 ${K.driveP} bar<br><span style="color:var(--sub);font-size:12px">轮毂 106 失氮 ⇒ 失电顺桨无源。SCADA 充能循环判据对 106 几乎不敏感(循环仅中度变化)——覆盖它的是机组自检容量检查(UP03284 组);两套监测互补,缺一即盲</span>`; }
- }
- function setMode(m){
- S.mode=m;
- document.querySelectorAll('#m-reg,#m-close,#m-open,#m-estop')
- .forEach(b=>b.classList.remove('on'));
- document.getElementById({reg:'m-reg',close:'m-close',open:'m-open',estop:'m-estop'}[m]).classList.add('on');
- const el=document.getElementById('verdict');
- if(m==='estop'){
- S.estop={t0:S.t,done:false,dV:0,pMin:S.pH};
- S.Vgas=(F.p0H>=0.5)? Math.max(K.V0H*F.p0H/K.pRelief, K.V0H*F.p0H/Math.max(S.pH,F.p0H+1)) : 0;
- el.className='verdict'; el.style.display='none';
- } else { el.className='verdict'; S.estop=null; }
- }
- const $=id=>document.getElementById(id);
- $('m-reg').onclick=()=>setMode('reg'); $('m-close').onclick=()=>setMode('close');
- $('m-open').onclick=()=>setMode('open'); $('m-estop').onclick=()=>setMode('estop');
- $('pw').onclick=()=>{ S.power=!S.power; $('pw').textContent=S.power?'有电':'失电';
- $('pw').classList.toggle('on',S.power); };
- $('spd').oninput=e=>{ $('spdv').textContent=e.target.value+'×'; };
- function bind(id,vid,set,fmt){ $(id).oninput=e=>{ set(+e.target.value);
- $(vid).textContent=fmt(+e.target.value); }; }
- bind('s-p070','v-p070',v=>F.p070=v,v=>v+' bar');
- bind('s-p0H','v-p0H',v=>F.p0H=v,v=>v+' bar');
- bind('s-leak','v-leak',v=>F.leak=v/10,v=>(v/10).toFixed(1)+'×');
- bind('s-pump','v-pump',v=>F.pump=v/100,v=>v+'%');
- bind('s-int','v-int',v=>F.inter=v/100,v=>v?(v+'%'):'关');
- $('s-stuck').onchange=e=>F.stuck=e.target.checked;
- const PRESETS={
- healthy: {p070:180,p0H:125,leak:10,pump:100,int:0,stuck:false},
- early: {p070:90, p0H:125,leak:10,pump:100,int:0,stuck:false},
- collapse:{p070:1, p0H:125,leak:10,pump:100,int:0,stuck:false},
- hubfail: {p070:180,p0H:1, leak:10,pump:100,int:0,stuck:false},
- bothfail:{p070:1, p0H:1, leak:10,pump:100,int:0,stuck:false},
- leak: {p070:180,p0H:125,leak:22,pump:100,int:0,stuck:false},
- pumpint: {p070:180,p0H:125,leak:10,pump:100,int:55,stuck:false},
- stuck: {p070:180,p0H:125,leak:10,pump:100,int:0,stuck:true},
- };
- document.querySelectorAll('.presets button').forEach(b=>b.onclick=()=>{
- const p=PRESETS[b.dataset.p];
- $('s-p070').value=p.p070; F.p070=p.p070; $('v-p070').textContent=p.p070+' bar';
- $('s-p0H').value=p.p0H; F.p0H=p.p0H; $('v-p0H').textContent=p.p0H+' bar';
- $('s-leak').value=p.leak; F.leak=p.leak/10; $('v-leak').textContent=(p.leak/10).toFixed(1)+'×';
- $('s-pump').value=p.pump; F.pump=p.pump/100; $('v-pump').textContent=p.pump+'%';
- $('s-int').value=p.int; F.inter=p.int/100; $('v-int').textContent=p.int?(p.int+'%'):'关';
- $('s-stuck').checked=p.stuck; F.stuck=p.stuck;
- S.cyc.length=0; S.lastVc=null; S.lastPer=null; S.ratchet=S.pH;
- });
- /* ================= 主循环 ================= */
- const cv=$('cv'), pv=$('pv');
- let last=performance.now(), carry=0;
- /* 表格 DOM 低频差量更新:每帧 innerHTML 重写会把翻译/监控类浏览器扩展的
- MutationObserver 打爆导致整页无响应(Chrome+扩展实测)。4 Hz + 无变化不写。 */
- let uiAcc=1, mxHtml='', ptHtml='';
- function frame(now){
- /* rAF 时间戳可早于 performance.now()(macOS26+Chrome151 实测: 首帧 now<last)
- → 负 dt 会让 while(n--) 从负数无限递减 = 死循环白屏。负值双重钳制。 */
- const dtR=Math.max(0, Math.min(0.06,(now-last)/1000)); last=now;
- const speed=+$('spd').value;
- carry+=dtR*speed; const DT=0.02;
- let n=Math.max(0, Math.min(400,Math.floor(carry/DT))); carry-=n*DT;
- if (carry<0) carry=0;
- while(n--) step(DT);
- try{ drawCircuit(); }catch(e){}
- try{ drawPiston(); }catch(e){}
- chart($('c1'),[
- {n:'叶片侧',c:css('--lp'),w:1.8,f:b=>b.pH},
- {n:'泵站侧',c:css('--hp'),w:1.6,f:b=>b.p}],
- 100,290,v=>v.toFixed(0),
- [[194,'194 加载',css('--ok')],[246,'246 卸荷',css('--hp')]],
- '系统压力 bar · 滚动 120 s');
- chart($('c2'),[{n:'θ',c:css('--ink'),w:1.8,f:b=>b.th}],
- -4,92,v=>v.toFixed(0),[[82,'82° 顺桨',css('--sub')]],'桨距角 °');
- chart($('c3'),[{n:'25号阀',c:css('--gas'),w:1.6,f:b=>b.v*0.9+0.05}],
- -0.08,1.15,v=>v>0.9?'得电':(v<0.1?'失电':''),[],'25 号卸荷阀状态(得电=加载)');
- uiAcc+=dtR;
- if (uiAcc>=0.25){ uiAcc=0;
- const h1=metrics().map(r=>
- `<tr><td>${r[0]}</td><td><b>${r[1]}</b></td><td style="color:var(--sub)">${r[2]}</td><td style="color:var(--hp)">${r[3]}</td></tr>`).join('');
- if (h1!==mxHtml){ mxHtml=h1; $('mx').innerHTML=h1; }
- const h2=pistonRows().map(r=>
- `<tr><td style="text-align:left;color:var(--sub)">${r[0]}</td><td><b>${r[1]}</b></td></tr>`).join('');
- if (h2!==ptHtml){ ptHtml=h2; $('pistab').innerHTML=h2; }
- }
- requestAnimationFrame(frame);
- }
- requestAnimationFrame(frame);
- </script>
|