feat: PLC遥测运算完善 + 调试页五区布局 + 比较元件 + MX输出注册
- plc.cpp: 新增MX_CMP比较元件(A>B出1), 减法改为有符号a-b, MX输出F32注册 - plc_config.js: 合并运算符按钮, MX pin A/B标注, 信号入/出按钮, 运算符面板 - plc_debug.js: 三列堆叠布局(左:遥信入·按钮/遥测入·设值, 中:信号表, 右:遥信出·LED/遥测出·展示) - ws_method.cpp: dc_save_out_signals_xml持久化 - style.css: 三列grid + block堆叠布局
This commit is contained in:
parent
a6f9384e66
commit
4edabf21d1
|
|
@ -358,7 +358,7 @@ a { color: var(--accent); text-decoration: none; }
|
|||
/* ============================================================
|
||||
PLC Debug
|
||||
============================================================ */
|
||||
.plc-debug-grid { display: grid; grid-template-columns: 1fr 1.2fr 1fr; gap: 16px; }
|
||||
.plc-debug-grid { display: grid; grid-template-columns: 1fr 1.2fr 1fr; gap: 8px; }
|
||||
.plc-col { min-width: 0; }
|
||||
.plc-sig-col { border-left: 1px solid var(--border); border-right: 1px solid var(--border); padding: 0 8px; }
|
||||
.plc-sig-list { max-height: 65vh; overflow-y: auto; }
|
||||
|
|
@ -419,7 +419,7 @@ a { color: var(--accent); text-decoration: none; }
|
|||
.plc-debug-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.2fr 1fr;
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
height: calc(100vh - 200px);
|
||||
min-height: 500px;
|
||||
}
|
||||
|
|
@ -446,6 +446,14 @@ a { color: var(--accent); text-decoration: none; }
|
|||
background: var(--bg-surface);
|
||||
position: sticky; top: 0; z-index: 1;
|
||||
}
|
||||
.plc-block { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
||||
.plc-block-header {
|
||||
padding: 8px 12px; font-size: 12px; font-weight: 700;
|
||||
color: var(--text-primary); border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-surface); display: flex; align-items: center;
|
||||
position: sticky; top: 0; z-index: 1;
|
||||
}
|
||||
|
||||
.plc-empty { text-align: center; color: var(--text-muted); padding: 30px 10px; font-size: 12px; }
|
||||
|
||||
.plc-item {
|
||||
|
|
@ -475,7 +483,7 @@ a { color: var(--accent); text-decoration: none; }
|
|||
.plc-item-val { margin-left: 4px; font-weight: 600; font-size: 10px; }
|
||||
|
||||
/* ---- PLC Signal List ---- */
|
||||
.plc-arrow-row { display: flex; gap: 6px; padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
||||
.plc-arrow-row { display: flex; gap: 4px; padding: 4px 8px; }
|
||||
.plc-sig-list { flex: 1; overflow-y: auto; }
|
||||
.plc-sig-check-item {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,70 +1,58 @@
|
|||
/**
|
||||
* @file plc_debug.js
|
||||
* @brief PLC 调试页 — 指示灯/按键绑定 out 信号 + 增量刷新
|
||||
* @brief PLC 调试页 — 三列布局:左(遥信输入·按钮/遥测输入·设值) | 中(信号表) | 右(遥信输出·LED/遥测输出·展示)
|
||||
* @details 所有条目从中间信号表选择添加,增量刷新
|
||||
*/
|
||||
var PlcDebugLeds = [];
|
||||
var PlcDebugButtons = [];
|
||||
var PlcDebugStIn = []; // 遥信输入(按钮)
|
||||
var PlcDebugMxIn = []; // 遥测输入(设值)
|
||||
var PlcDebugStOut = []; // 遥信输出(LED)
|
||||
var PlcDebugMxOut = []; // 遥测输出(展示)
|
||||
|
||||
Router.register('#plc_debug', function(c) { PlcDebugPage.render(c); });
|
||||
|
||||
var PlcDebugPage = (function() {
|
||||
var outSignals = [];
|
||||
var ledChecked = {}, btnChecked = {}, sigChecked = {};
|
||||
var stInChk = {}, mxInChk = {}, stOutChk = {}, mxOutChk = {}, sigChk = {};
|
||||
|
||||
function getOut() {
|
||||
var out = SignalStore.get('out');
|
||||
return (Array.isArray(out) && out.length) ? out : outSignals;
|
||||
}
|
||||
|
||||
function showPicker(cb) {
|
||||
var sigs = getOut();
|
||||
if (!sigs.length) { toast(I18n.t('plc_debug.no_sig'), 'error'); return; }
|
||||
var rows = '';
|
||||
sigs.forEach(function(s) {
|
||||
rows += '<div class="plc-pick-row" data-saddr="' + esc(s.saddr) + '" data-desc="' + esc(s.desc || '') + '">'
|
||||
+ '<strong>' + esc(s.saddr) + '</strong>'
|
||||
+ (s.desc ? ' <span style="color:var(--text-muted)">' + esc(s.desc) + '</span>' : '')
|
||||
+ '<span style="float:right;color:var(--accent);font-weight:700">' + fmtVal(s.val) + '</span></div>';
|
||||
});
|
||||
var ov = document.createElement('div');
|
||||
ov.className = 'modal-overlay';
|
||||
ov.innerHTML = '<div class="modal-box" style="max-width:550px"><h3>' + I18n.t('plc_debug.bind_title') + '</h3>'
|
||||
+ '<div style="max-height:300px;overflow-y:auto;margin:12px 0">' + rows + '</div>'
|
||||
+ '<button class="btn btn-sm" id="plc-pick-cancel">' + I18n.t('toast.cancel') + '</button></div>';
|
||||
document.body.appendChild(ov);
|
||||
document.getElementById('plc-pick-cancel').onclick = function() { ov.remove(); };
|
||||
ov.addEventListener('click', function(e) { if (e.target === ov) ov.remove(); });
|
||||
ov.querySelectorAll('.plc-pick-row').forEach(function(r) {
|
||||
r.onclick = function() {
|
||||
var sa = this.getAttribute('data-saddr'), de = this.getAttribute('data-desc');
|
||||
ov.remove();
|
||||
if (confirm(I18n.t('plc_debug.confirm_bind') + ' ' + sa + ' ?')) cb(sa, de);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function findSig(sa) {
|
||||
for (var i = 0; i < outSignals.length; i++) if (outSignals[i].saddr === sa) return outSignals[i];
|
||||
for (var i = 0; i < outSignals.length; i++)
|
||||
if (outSignals[i].saddr === sa) return outSignals[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
var o = getOut(); if (Array.isArray(o)) outSignals = o;
|
||||
PlcDebugLeds.forEach(function(l) { var f = findSig(l.saddr); l.val = f ? f.val : 0; });
|
||||
PlcDebugButtons.forEach(function(b) { var f = findSig(b.saddr); b.val = f ? f.val : 0; });
|
||||
PlcDebugStIn.forEach(function(l) { var f = findSig(l.saddr); l.val = f ? f.val : 0; });
|
||||
PlcDebugMxIn.forEach(function(m) { var f = findSig(m.saddr); m.val = f ? f.val : 0; });
|
||||
PlcDebugStOut.forEach(function(b) { var f = findSig(b.saddr); b.val = f ? f.val : 0; });
|
||||
PlcDebugMxOut.forEach(function(m) { var f = findSig(m.saddr); m.val = f ? f.val : 0; });
|
||||
}
|
||||
|
||||
function incrRefresh() {
|
||||
refreshData();
|
||||
PlcDebugLeds.forEach(function(l, i) {
|
||||
PlcDebugStIn.forEach(function(b, i) {
|
||||
var on = parseFloat(b.val) > 0;
|
||||
var ve = document.getElementById('sti-val-' + i);
|
||||
if (ve) { ve.textContent = '(' + fmtVal(b.val) + ')'; ve.style.color = on ? 'var(--accent)' : 'var(--text-muted)'; }
|
||||
});
|
||||
PlcDebugMxIn.forEach(function(m, i) {
|
||||
var ve = document.getElementById('mxi-cur-' + i);
|
||||
if (ve) ve.textContent = '(' + fmtVal(m.val) + ')';
|
||||
});
|
||||
PlcDebugStOut.forEach(function(l, i) {
|
||||
var on = parseFloat(l.val) > 0;
|
||||
var ve = document.getElementById('led-val-' + i), ie = document.getElementById('led-icon-' + i);
|
||||
if (ve) { ve.textContent = '(' + l.val + ')'; ve.style.color = on ? 'var(--accent)' : 'var(--text-muted)'; }
|
||||
var ve = document.getElementById('sto-val-' + i), ie = document.getElementById('sto-icon-' + i);
|
||||
if (ve) { ve.textContent = '(' + fmtVal(l.val) + ')'; ve.style.color = on ? 'var(--accent)' : 'var(--text-muted)'; }
|
||||
if (ie) ie.className = 'plc-led' + (on ? ' plc-led-on' : '');
|
||||
});
|
||||
PlcDebugButtons.forEach(function(b, j) {
|
||||
var ve = document.getElementById('btn-val-' + j);
|
||||
if (ve) { ve.textContent = '(' + b.val + ')'; ve.style.color = parseFloat(b.val) > 0 ? 'var(--accent)' : 'var(--text-muted)'; }
|
||||
PlcDebugMxOut.forEach(function(m, i) {
|
||||
var ve = document.getElementById('mxo-val-' + i);
|
||||
if (ve) ve.textContent = fmtVal(m.val);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -72,111 +60,168 @@ var PlcDebugPage = (function() {
|
|||
|
||||
function delChecked() {
|
||||
var todel = [];
|
||||
for (var k in ledChecked) if (ledChecked.hasOwnProperty(k)) todel.push({ type: 'led', idx: parseInt(k) });
|
||||
for (var k in btnChecked) if (btnChecked.hasOwnProperty(k)) todel.push({ type: 'btn', idx: parseInt(k) });
|
||||
for (var k in stInChk) if (stInChk.hasOwnProperty(k)) todel.push({ t: 'sti', idx: parseInt(k) });
|
||||
for (var k in mxInChk) if (mxInChk.hasOwnProperty(k)) todel.push({ t: 'mxi', idx: parseInt(k) });
|
||||
for (var k in stOutChk) if (stOutChk.hasOwnProperty(k)) todel.push({ t: 'sto', idx: parseInt(k) });
|
||||
for (var k in mxOutChk) if (mxOutChk.hasOwnProperty(k)) todel.push({ t: 'mxo', idx: parseInt(k) });
|
||||
if (!todel.length) { toast(I18n.t('toast.no_selected'), 'error'); return; }
|
||||
if (!confirm(I18n.t('plc_debug.confirm_del') + ' ' + todel.length + ' ?')) return;
|
||||
todel.sort(function(a, b) { return b.idx - a.idx; }).forEach(function(t) {
|
||||
if (t.type === 'led') PlcDebugLeds.splice(t.idx, 1);
|
||||
else PlcDebugButtons.splice(t.idx, 1);
|
||||
if (t.t === 'sti') PlcDebugStIn.splice(t.idx, 1);
|
||||
else if (t.t === 'mxi') PlcDebugMxIn.splice(t.idx, 1);
|
||||
else if (t.t === 'sto') PlcDebugStOut.splice(t.idx, 1);
|
||||
else PlcDebugMxOut.splice(t.idx, 1);
|
||||
});
|
||||
ledChecked = {}; btnChecked = {};
|
||||
stInChk = {}; mxInChk = {}; stOutChk = {}; mxOutChk = {};
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function makeBlock(title, icon, arr, chkObj, idPrefix, blockType) {
|
||||
var html = '';
|
||||
if (!arr.length) {
|
||||
html = '<div style="color:var(--text-muted);padding:16px;text-align:center;font-size:12px">暂无</div>';
|
||||
} else {
|
||||
arr.forEach(function(item, i) {
|
||||
var on = parseFloat(item.val) > 0, chk = !!chkObj[i];
|
||||
html += '<div class="plc-item">';
|
||||
html += '<input type="checkbox" ' + (chk ? 'checked' : '') + ' onchange="PlcDebugPage._toggleChk(\'' + idPrefix + '\',' + i + ',this.checked)">';
|
||||
|
||||
if (blockType === 'btn') {
|
||||
html += '<button class="plc-btn-icon" id="' + idPrefix + '-icon-' + i + '" title="点击翻转">🔘</button>';
|
||||
} else if (blockType === 'led') {
|
||||
html += '<div class="plc-led' + (on ? ' plc-led-on' : '') + '" id="' + idPrefix + '-icon-' + i + '"></div>';
|
||||
}
|
||||
|
||||
html += '<div class="plc-info">';
|
||||
if (item.saddr) {
|
||||
html += '<span class="plc-saddr">' + esc(item.saddr) + '</span>';
|
||||
if (item.desc) html += '<span style="color:var(--text-muted);font-size:11px">' + esc(item.desc) + '</span>';
|
||||
} else {
|
||||
html += '<span style="color:var(--text-muted)">未绑定</span>';
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (blockType === 'btn' || blockType === 'led') {
|
||||
html += '<span id="' + idPrefix + '-val-' + i + '" style="color:' + (on ? 'var(--accent)' : 'var(--text-muted)') + ';font-weight:600;font-family:var(--font-mono);font-size:12px">(' + fmtVal(item.val) + ')</span>';
|
||||
} else if (blockType === 'mxi') {
|
||||
html += '<span id="' + idPrefix + '-cur-' + i + '" style="color:var(--text-muted);font-weight:600;font-family:var(--font-mono);font-size:11px;margin-right:4px">(' + fmtVal(item.val) + ')</span>';
|
||||
html += '<input type="text" id="' + idPrefix + '-input-' + i + '" placeholder="输入值..." style="width:60px;padding:2px 4px;border:1px solid var(--border);border-radius:3px;background:var(--bg-input);color:var(--text-primary);font-size:11px;font-family:var(--font-mono)">';
|
||||
html += '<button class="btn btn-xs" style="font-size:10px;padding:1px 6px" id="' + idPrefix + '-setbtn-' + i + '">设置</button>';
|
||||
} else if (blockType === 'mxo') {
|
||||
html += '<span id="' + idPrefix + '-val-' + i + '" style="color:var(--accent);font-weight:700;font-family:var(--font-mono);font-size:12px">' + fmtVal(item.val) + '</span>';
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
}
|
||||
return '<div class="plc-block"><div class="plc-block-header"><span>' + icon + ' ' + title + '</span>'
|
||||
+ '<span style="margin-left:auto;font-size:10px;color:var(--text-muted)">' + arr.length + '</span></div>'
|
||||
+ '<div class="plc-sig-list">' + html + '</div></div>';
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
var c = document.getElementById('plc-content');
|
||||
if (!c) return;
|
||||
refreshData();
|
||||
|
||||
/* 指示灯列 */
|
||||
var ledHtml = '';
|
||||
if (!PlcDebugLeds.length) {
|
||||
ledHtml = '<div style="color:var(--text-muted);padding:20px;text-align:center">' + I18n.t('plc_debug.no_led') + '</div>';
|
||||
} else {
|
||||
PlcDebugLeds.forEach(function(l, i) {
|
||||
var on = parseFloat(l.val) > 0, chk = !!ledChecked[i];
|
||||
ledHtml += '<div class="plc-item">'
|
||||
+ '<div class="plc-led' + (on ? ' plc-led-on' : '') + '" id="led-icon-' + i + '" onclick="PlcDebugPage._bindLed(' + i + ')" title="' + I18n.t('plc_debug.click_to_bind') + '"></div>'
|
||||
+ '<div class="plc-info">'
|
||||
+ (l.saddr ? '<span class="plc-saddr">' + esc(l.saddr) + ' <span id="led-val-' + i + '" style="color:' + (on ? 'var(--accent)' : 'var(--text-muted)') + '">(' + fmtVal(l.val) + ')</span></span>'
|
||||
+ (l.desc ? '<span style="color:var(--text-muted);font-size:11px">' + esc(l.desc) + '</span>' : '')
|
||||
: '<span style="color:var(--text-muted)">' + I18n.t('plc_debug.unbound') + '</span>')
|
||||
+ '</div>'
|
||||
+ '<input type="checkbox" ' + (chk ? 'checked' : '') + ' onchange="PlcDebugPage._toggleLed(' + i + ',this.checked)">'
|
||||
+ '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
/* 按键列 */
|
||||
var btnHtml = '';
|
||||
if (!PlcDebugButtons.length) {
|
||||
btnHtml = '<div style="color:var(--text-muted);padding:20px;text-align:center">' + I18n.t('plc_debug.no_btn') + '</div>';
|
||||
} else {
|
||||
PlcDebugButtons.forEach(function(b, j) {
|
||||
var chk = !!btnChecked[j];
|
||||
btnHtml += '<div class="plc-item">'
|
||||
+ '<button class="plc-btn-icon" id="btn-icon-' + j + '" onclick="PlcDebugPage._toggleBtn(' + j + ')" title="' + I18n.t('plc_debug.toggle') + '">🔘</button>'
|
||||
+ '<div class="plc-info">'
|
||||
+ (b.saddr ? '<span class="plc-saddr">' + esc(b.saddr) + ' <span id="btn-val-' + j + '" style="color:var(--text-muted)">(' + fmtVal(b.val) + ')</span></span>'
|
||||
+ (b.desc ? '<span style="color:var(--text-muted);font-size:11px">' + esc(b.desc) + '</span>' : '')
|
||||
: '<span style="color:var(--text-muted)">' + I18n.t('plc_debug.unbound') + '</span>')
|
||||
+ '</div>'
|
||||
+ '<input type="checkbox" ' + (chk ? 'checked' : '') + ' onchange="PlcDebugPage._toggleBtnChk(' + j + ',this.checked)">'
|
||||
+ '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
/* 信号列 */
|
||||
/* 信号表 (中间列) */
|
||||
var o = getOut();
|
||||
var sigHtml = '';
|
||||
if (!o.length) {
|
||||
sigHtml = '<div style="color:var(--text-muted);padding:20px;text-align:center">' + I18n.t('plc_debug.no_sig') + '</div>';
|
||||
sigHtml = '<div style="color:var(--text-muted);padding:20px;text-align:center;font-size:12px">' + I18n.t('plc_debug.no_sig') + '</div>';
|
||||
} else {
|
||||
o.forEach(function(s, i) {
|
||||
var chk = !!sigChecked[i];
|
||||
sigHtml += '<div class="plc-item">'
|
||||
+ '<input type="checkbox" ' + (chk ? 'checked' : '') + ' onchange="PlcDebugPage._toggleSig(' + i + ',this.checked)">'
|
||||
+ '<span class="plc-saddr">' + esc(s.saddr) + '</span>'
|
||||
+ '<span style="color:var(--text-muted);font-size:11px">' + esc(s.desc || '') + '</span>'
|
||||
+ '<span style="margin-left:auto;color:var(--accent);font-weight:700;font-family:var(--font-mono)">' + fmtVal(s.val) + '</span>'
|
||||
var chk = !!sigChk[i];
|
||||
sigHtml += '<div class="plc-sig-check-item" data-idx="' + i + '">'
|
||||
+ '<input type="checkbox" ' + (chk ? 'checked' : '') + ' data-sig-idx="' + i + '">'
|
||||
+ '<span style="color:var(--accent);min-width:24px;font-size:12px;font-weight:600">#' + i + '</span>'
|
||||
+ '<span class="plc-sig-saddr" title="' + esc(s.saddr) + (s.desc ? ' - ' + esc(s.desc) : '') + '" style="font-size:12px">' + esc(s.saddr) + '</span>'
|
||||
+ (s.desc ? '<span style="color:var(--text-muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(s.desc) + '</span>' : '')
|
||||
+ '<span style="margin-left:auto;color:var(--accent);font-weight:700;font-family:var(--font-mono);font-size:12px">' + fmtVal(s.val) + '</span>'
|
||||
+ '</div>';
|
||||
});
|
||||
}
|
||||
|
||||
c.innerHTML = '<div class="plc-debug-grid">'
|
||||
+ '<div class="plc-col"><div class="card-header" style="margin-bottom:8px"><span>💡 ' + I18n.t('plc_debug.led_col') + '</span>'
|
||||
+ '<div class="btn-group">'
|
||||
+ '<button class="btn btn-sm" onclick="PlcDebugPage._addLed()">' + I18n.t('plc_debug.add_led') + '</button>'
|
||||
+ '</div></div><div class="plc-sig-list">' + ledHtml + '</div></div>'
|
||||
+ '<div class="plc-col plc-sig-col"><div class="card-header" style="margin-bottom:8px"><span>📋 ' + I18n.t('plc_debug.sig_col') + '</span>'
|
||||
+ '<div class="plc-arrow-row">'
|
||||
+ '<button class="btn btn-sm" onclick="PlcDebugPage._moveSel(\'led\')" title="' + I18n.t('plc_debug.to_led') + '">← LED</button>'
|
||||
+ '<button class="btn btn-sm" onclick="PlcDebugPage._moveSel(\'btn\')" title="' + I18n.t('plc_debug.to_btn') + '">→ BTN</button>'
|
||||
+ '</div></div><div class="plc-sig-list">' + sigHtml + '</div></div>'
|
||||
+ '<div class="plc-col"><div class="card-header" style="margin-bottom:8px"><span>🔘 ' + I18n.t('plc_debug.btn_col') + '</span>'
|
||||
+ '<div class="btn-group">'
|
||||
+ '<button class="btn btn-sm" onclick="PlcDebugPage._addBtn()">' + I18n.t('plc_debug.add_btn') + '</button>'
|
||||
+ '</div></div><div class="plc-sig-list">' + btnHtml + '</div></div>'
|
||||
/* 左列: 遥信输入(上) + 遥测输入(下) */
|
||||
+ '<div class="plc-col" style="display:flex;flex-direction:column;gap:8px">'
|
||||
+ makeBlock('遥信输入', '📤', PlcDebugStIn, stInChk, 'sti', 'btn')
|
||||
+ makeBlock('遥测输入', '📊', PlcDebugMxIn, mxInChk, 'mxi', 'mxi')
|
||||
+ '</div>'
|
||||
/* 中列: 信号表 */
|
||||
+ '<div class="plc-col plc-sig-col"><div class="card-header" style="margin-bottom:6px"><span>📋 信号表</span>'
|
||||
+ '<span style="margin-left:auto;font-size:11px;color:var(--text-muted)">' + o.length + '</span>'
|
||||
+ '</div>'
|
||||
+ '<div class="plc-arrow-row" style="justify-content:space-between">'
|
||||
+ '<button class="btn btn-xs" style="font-size:10px;padding:2px 8px" onclick="PlcDebugPage._moveSel(\'sti\')" title="→遥信输入">← 遥信输入</button>'
|
||||
+ '<button class="btn btn-xs" style="font-size:10px;padding:2px 8px" onclick="PlcDebugPage._moveSel(\'sto\')" title="→遥信输出">遥信输出 →</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="plc-arrow-row" style="justify-content:space-between;margin-top:2px">'
|
||||
+ '<button class="btn btn-xs" style="font-size:10px;padding:2px 8px" onclick="PlcDebugPage._moveSel(\'mxi\')" title="→遥测输入">← 遥测输入</button>'
|
||||
+ '<button class="btn btn-xs" style="font-size:10px;padding:2px 8px" onclick="PlcDebugPage._moveSel(\'mxo\')" title="→遥测输出">遥测输出 →</button>'
|
||||
+ '</div>'
|
||||
+ '<div class="plc-sig-list">' + sigHtml + '</div></div>'
|
||||
/* 右列: 遥信输出(上) + 遥测输出(下) */
|
||||
+ '<div class="plc-col" style="display:flex;flex-direction:column;gap:8px">'
|
||||
+ makeBlock('遥信输出', '📥', PlcDebugStOut, stOutChk, 'sto', 'led')
|
||||
+ makeBlock('遥测输出', '📊', PlcDebugMxOut, mxOutChk, 'mxo', 'mxo')
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
|
||||
/* 绑定事件 */
|
||||
[].forEach.call(c.querySelectorAll('.plc-btn-icon'), function(el) {
|
||||
el.onclick = function() {
|
||||
var m = this.id.match(/^sti-icon-(\d+)$/);
|
||||
if (m) PlcDebugPage._toggleStIn(parseInt(m[1]));
|
||||
};
|
||||
});
|
||||
[].forEach.call(c.querySelectorAll('.plc-led'), function(el) {
|
||||
el.onclick = function() {
|
||||
var m = this.id.match(/^sto-icon-(\d+)$/);
|
||||
if (m) PlcDebugPage._bindStOut(parseInt(m[1]));
|
||||
};
|
||||
});
|
||||
[].forEach.call(c.querySelectorAll('[id$="-setbtn-"]'), function(el) {
|
||||
el.onclick = function() {
|
||||
var m = this.id.match(/^mxi-setbtn-(\d+)$/);
|
||||
if (m) PlcDebugPage._setMxIn(parseInt(m[1]));
|
||||
};
|
||||
});
|
||||
[].forEach.call(c.querySelectorAll('.plc-sig-check-item'), function(row) {
|
||||
row.onclick = function(e) {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
var cb = this.querySelector('input[type="checkbox"]');
|
||||
if (cb) {
|
||||
cb.checked = !cb.checked;
|
||||
var si = parseInt(cb.getAttribute('data-sig-idx'));
|
||||
if (!isNaN(si)) { if (cb.checked) sigChk[si] = true; else delete sigChk[si]; }
|
||||
}
|
||||
};
|
||||
});
|
||||
[].forEach.call(c.querySelectorAll('.plc-sig-check-item input[type="checkbox"]'), function(cb) {
|
||||
cb.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
var si = parseInt(this.getAttribute('data-sig-idx'));
|
||||
if (!isNaN(si)) { if (this.checked) sigChk[si] = true; else delete sigChk[si]; }
|
||||
};
|
||||
});
|
||||
|
||||
updateDelBtn();
|
||||
}
|
||||
|
||||
function updateDelBtn() {
|
||||
var el = document.getElementById('plc-del-btn');
|
||||
if (!el) return;
|
||||
var n = countChecked(ledChecked) + countChecked(btnChecked);
|
||||
var n = countChecked(stInChk) + countChecked(mxInChk) + countChecked(stOutChk) + countChecked(mxOutChk);
|
||||
el.textContent = I18n.t('plc_debug.del_sel') + (n ? ' (' + n + ')' : '');
|
||||
el.disabled = !n;
|
||||
}
|
||||
|
||||
return {
|
||||
render: function(c) {
|
||||
ledChecked = {}; btnChecked = {}; sigChecked = {};
|
||||
stInChk = {}; mxInChk = {}; stOutChk = {}; mxOutChk = {}; sigChk = {};
|
||||
refreshData();
|
||||
c.innerHTML = '<div class="card">'
|
||||
+ '<div class="card-header"><span>' + I18n.t('plc_debug.title') + '</span>'
|
||||
+ '<div class="card-header"><span>📟 ' + I18n.t('plc_debug.title') + '</span>'
|
||||
+ '<div class="btn-group">'
|
||||
+ '<button class="btn btn-sm" onclick="PlcDebugPage._refresh()">🔄 ' + I18n.t('plc_debug.refresh_btn') + '</button>'
|
||||
+ '<button class="btn btn-danger btn-sm" id="plc-del-btn" disabled onclick="PlcDebugPage._delChecked()">🗑 ' + I18n.t('plc_debug.del_sel') + '</button>'
|
||||
|
|
@ -184,38 +229,105 @@ var PlcDebugPage = (function() {
|
|||
+ '<div id="plc-content"></div></div>';
|
||||
renderAll();
|
||||
},
|
||||
_addLed: function() { PlcDebugLeds.push({ saddr: '', desc: '', val: 0 }); renderAll(); },
|
||||
_addBtn: function() { PlcDebugButtons.push({ saddr: '', desc: '', val: 0 }); renderAll(); },
|
||||
_bindLed: function(i) { showPicker(function(sa, de) { PlcDebugLeds[i].saddr = sa; PlcDebugLeds[i].desc = de; var f = findSig(sa); if (f) PlcDebugLeds[i].val = f.val; renderAll(); }); },
|
||||
_toggleBtn: function(j) {
|
||||
var b = PlcDebugButtons[j];
|
||||
if (!b.saddr) { showPicker(function(sa, de) { b.saddr = sa; b.desc = de; var f = findSig(sa); if (f) b.val = f.val; renderAll(); }); return; }
|
||||
_toggleChk: function(prefix, i, v) {
|
||||
if (prefix === 'sti') { if (v) stInChk[i] = true; else delete stInChk[i]; }
|
||||
if (prefix === 'mxi') { if (v) mxInChk[i] = true; else delete mxInChk[i]; }
|
||||
if (prefix === 'sto') { if (v) stOutChk[i] = true; else delete stOutChk[i]; }
|
||||
if (prefix === 'mxo') { if (v) mxOutChk[i] = true; else delete mxOutChk[i]; }
|
||||
updateDelBtn();
|
||||
},
|
||||
_toggleStIn: function(i) {
|
||||
var b = PlcDebugStIn[i];
|
||||
if (!b.saddr) {
|
||||
PlcDebugPage._showPicker(function(sa, de) {
|
||||
b.saddr = sa; b.desc = de;
|
||||
var f = findSig(sa); if (f) b.val = f.val;
|
||||
renderAll();
|
||||
});
|
||||
return;
|
||||
}
|
||||
var nv = parseFloat(b.val) > 0 ? 0 : 1;
|
||||
WsClient.send({ curd: 'set', signal_type: 'out', saddr: b.saddr, signal_data: String(nv), setting_zone: '0' });
|
||||
b.val = nv;
|
||||
/* 同步写入 SignalStore,确保 refreshData() 不覆盖新值 */
|
||||
var outs = SignalStore.get('out');
|
||||
for (var i = 0; i < outs.length; i++) {
|
||||
if (outs[i].saddr === b.saddr) { outs[i].val = String(nv); break; }
|
||||
}
|
||||
for (var j = 0; j < outs.length; j++)
|
||||
if (outs[j].saddr === b.saddr) { outs[j].val = String(nv); break; }
|
||||
renderAll();
|
||||
},
|
||||
_toggleLed: function(i, v) { ledChecked[i] = v; updateDelBtn(); },
|
||||
_toggleBtnChk: function(j, v) { btnChecked[j] = v; updateDelBtn(); },
|
||||
_toggleSig: function(i, v) { sigChecked[i] = v; },
|
||||
_bindStOut: function(i) {
|
||||
PlcDebugPage._showPicker(function(sa, de) {
|
||||
PlcDebugStOut[i].saddr = sa; PlcDebugStOut[i].desc = de;
|
||||
var f = findSig(sa); if (f) PlcDebugStOut[i].val = f.val;
|
||||
renderAll();
|
||||
});
|
||||
},
|
||||
_setMxIn: function(i) {
|
||||
var m = PlcDebugMxIn[i];
|
||||
if (!m || !m.saddr) {
|
||||
PlcDebugPage._showPicker(function(sa, de) {
|
||||
if (!PlcDebugMxIn[i]) return;
|
||||
PlcDebugMxIn[i].saddr = sa; PlcDebugMxIn[i].desc = de;
|
||||
var f = findSig(sa); if (f) PlcDebugMxIn[i].val = f.val;
|
||||
renderAll();
|
||||
});
|
||||
return;
|
||||
}
|
||||
var inp = document.getElementById('mxi-input-' + i);
|
||||
if (!inp) { toast('找不到输入框', 'error'); return; }
|
||||
var nv = inp.value.trim();
|
||||
if (nv === '') { toast('请输入值', 'error'); return; }
|
||||
var sent = WsClient.send({ curd: 'set', signal_type: 'out', saddr: m.saddr, signal_data: nv, setting_zone: '0' });
|
||||
if (!sent) { toast('发送失败', 'error'); return; }
|
||||
m.val = parseFloat(nv) || 0;
|
||||
m.inputVal = '';
|
||||
var outs = SignalStore.get('out');
|
||||
for (var j = 0; j < outs.length; j++)
|
||||
if (outs[j].saddr === m.saddr) { outs[j].val = nv; break; }
|
||||
renderAll();
|
||||
},
|
||||
_showPicker: function(cb) {
|
||||
var sigs = getOut();
|
||||
if (!sigs.length) { toast(I18n.t('plc_debug.no_sig'), 'error'); return; }
|
||||
var rows = '';
|
||||
sigs.forEach(function(s) {
|
||||
rows += '<div class="plc-pick-row" data-saddr="' + esc(s.saddr) + '" data-desc="' + esc(s.desc || '') + '">'
|
||||
+ '<strong>' + esc(s.saddr) + '</strong>'
|
||||
+ (s.desc ? ' <span style="color:var(--text-muted)">' + esc(s.desc) + '</span>' : '')
|
||||
+ '<span style="float:right;color:var(--accent);font-weight:700">' + fmtVal(s.val) + '</span></div>';
|
||||
});
|
||||
var ov = document.createElement('div');
|
||||
ov.className = 'modal-overlay';
|
||||
ov.innerHTML = '<div class="modal-box" style="max-width:550px"><h3>' + I18n.t('plc_debug.bind_title') + '</h3>'
|
||||
+ '<div style="max-height:300px;overflow-y:auto;margin:12px 0">' + rows + '</div>'
|
||||
+ '<button class="btn btn-sm" id="plc-pick-cancel">' + I18n.t('toast.cancel') + '</button></div>';
|
||||
document.body.appendChild(ov);
|
||||
document.getElementById('plc-pick-cancel').onclick = function() { ov.remove(); };
|
||||
ov.addEventListener('click', function(e) { if (e.target === ov) ov.remove(); });
|
||||
ov.querySelectorAll('.plc-pick-row').forEach(function(r) {
|
||||
r.onclick = function() {
|
||||
var sa = this.getAttribute('data-saddr'), de = this.getAttribute('data-desc');
|
||||
ov.remove();
|
||||
if (confirm(I18n.t('plc_debug.confirm_bind') + ' ' + sa + ' ?')) cb(sa, de);
|
||||
};
|
||||
});
|
||||
},
|
||||
_moveSel: function(target) {
|
||||
var picked = [];
|
||||
for (var k in sigChecked) if (sigChecked.hasOwnProperty(k)) picked.push(parseInt(k));
|
||||
for (var k in sigChk) if (sigChk.hasOwnProperty(k)) picked.push(parseInt(k));
|
||||
if (!picked.length) { toast(I18n.t('toast.no_selected'), 'error'); return; }
|
||||
picked.sort(function(a, b) { return a - b; }).forEach(function(i) {
|
||||
var s = outSignals[i];
|
||||
if (!s) return;
|
||||
if (target === 'led') PlcDebugLeds.push({ saddr: s.saddr, desc: s.desc || '', val: s.val || 0 });
|
||||
else PlcDebugButtons.push({ saddr: s.saddr, desc: s.desc || '', val: s.val || 0 });
|
||||
var item = { saddr: s.saddr, desc: s.desc || '', val: s.val || 0 };
|
||||
if (target === 'mxi') item.inputVal = '';
|
||||
if (target === 'sti') PlcDebugStIn.push(item);
|
||||
else if (target === 'sto') PlcDebugStOut.push(item);
|
||||
else if (target === 'mxi') PlcDebugMxIn.push(item);
|
||||
else PlcDebugMxOut.push(item);
|
||||
});
|
||||
sigChecked = {}; renderAll();
|
||||
sigChk = {}; renderAll();
|
||||
},
|
||||
_refresh: function() { refreshData(); incrRefresh(); },
|
||||
_refresh: function() { refreshData(); renderAll(); },
|
||||
incrRefresh: function() { refreshData(); incrRefresh(); },
|
||||
_delChecked: delChecked,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,9 +51,12 @@ static int g_plc_dbg_enabled = 0; /**< PLC 调试打印开关, 0=关闭, 1=打
|
|||
* 模块常量 & 全局变量
|
||||
* ================================================================== */
|
||||
|
||||
#define MAX_OUT_NODES 20 /**< 最大输出节点数 */
|
||||
#define MAX_OUT_ST_NODES 20 /**< 最大遥信输出节点数 */
|
||||
#define MAX_OUT_MX_NODES 20 /**< 最大遥测输出节点数 */
|
||||
|
||||
LOCAL uint8_t g_plc_st_out[MAX_OUT_NODES] = {0};
|
||||
|
||||
LOCAL uint8_t g_plc_st_out[MAX_OUT_ST_NODES] = {0};
|
||||
LOCAL float g_plc_mx_out[MAX_OUT_MX_NODES] = {0.0f};
|
||||
LOCAL uint32_t *gp_run_cnt_in = NULL;
|
||||
LOCAL uint8_t *gp_st[10] = {NULL};
|
||||
|
||||
|
|
@ -87,6 +90,15 @@ std::vector<stru_plc_cfg> g_plc_cfg = {};
|
|||
#define NODE_TYPE_SR_LATCH 8 /**< SR 触发器 (S 优先) */
|
||||
#define NODE_TYPE_RS_LATCH 9 /**< RS 触发器 (R 优先) */
|
||||
#define NODE_TYPE_FALLING_EDGE 10 /**< 下降沿检测 (1→0 脉冲) */
|
||||
#define NODE_TYPE_TOGGLE 11 /**< 翻转元件 (输入变化时输出翻转) */
|
||||
#define NODE_TYPE_MX_ADD 50 /**< 遥测加法元件 (两输入求和) */
|
||||
#define NODE_TYPE_MX_SUB 51 /**< 遥测减法元件 (A - B,可负数) */
|
||||
#define NODE_TYPE_MX_MAX 52 /**< 遥测最大值元件 (两输入取大) */
|
||||
#define NODE_TYPE_MX_MIN 53 /**< 遥测最小值元件 (两输入取小) */
|
||||
#define NODE_TYPE_MX_MUL 54 /**< 遥测乘法元件 (两输入之积) */
|
||||
#define NODE_TYPE_MX_DIV 55 /**< 遥测除法元件 (两输入之商) */
|
||||
#define NODE_TYPE_MX_ABS 56 /**< 遥测绝对值元件 (单输入取绝对值) */
|
||||
#define NODE_TYPE_MX_CMP 57 /**< 遥测比较元件 (A>B 出1,否则出0) */
|
||||
|
||||
/** 动态链表 — 硬件点 (运行时 I/O 状态) */
|
||||
typedef struct HWPoint
|
||||
|
|
@ -134,7 +146,7 @@ typedef struct
|
|||
int src_id;
|
||||
int dest_type;
|
||||
int dest_id;
|
||||
int dst_pin; /**< 目标引脚 (0=S, 1=R, -1=默认), 仅 SR/RS 有效 */
|
||||
int dst_pin; /**< 目标引脚 (0=A/S, 1=B/R, -1=默认) */
|
||||
} LogicLink;
|
||||
|
||||
/** 逻辑图 — 一张完整的 PLC 逻辑图 */
|
||||
|
|
@ -183,6 +195,14 @@ LOCAL int plc_set_out_by_id(int id, int value)
|
|||
return -1;
|
||||
}
|
||||
|
||||
/* 遥测输出 (plc.mx.out.*): DATA_TYPE_F32,需要完整浮点值 */
|
||||
if (strstr(saddr, "plc.mx.") == saddr)
|
||||
{
|
||||
float fval = (float)value;
|
||||
return dc_set_out_signal_val(saddr, &fval, "plc");
|
||||
}
|
||||
|
||||
|
||||
uint8_t val = (uint8_t)(value ? 1 : 0);
|
||||
return dc_set_out_signal_val(saddr, &val, "plc");
|
||||
}
|
||||
|
|
@ -223,15 +243,15 @@ LOCAL int plc_get_out_value_by_id(int id)
|
|||
}
|
||||
else if (data_type == DATA_TYPE_U16)
|
||||
{
|
||||
return (*(uint16_t *)p_data) ? 1 : 0;
|
||||
return *(uint16_t *)p_data;
|
||||
}
|
||||
else if (data_type == DATA_TYPE_U32)
|
||||
{
|
||||
return (*(uint32_t *)p_data) ? 1 : 0;
|
||||
return *(uint32_t *)p_data;
|
||||
}
|
||||
else if (data_type == DATA_TYPE_F32)
|
||||
{
|
||||
return (*(float *)p_data > 0.5f) ? 1 : 0;
|
||||
return (int)(*(float *)p_data);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -451,6 +471,42 @@ static int gate_type_from_xml_str(const char *type_str)
|
|||
return NODE_TYPE_FALLING_EDGE;
|
||||
}
|
||||
|
||||
if (strcmp(type_str, "TOGGLE") == 0)
|
||||
{
|
||||
return NODE_TYPE_TOGGLE;
|
||||
}
|
||||
if (strcmp(type_str, "MX_ADD") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_ADD;
|
||||
}
|
||||
if (strcmp(type_str, "MX_SUB") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_SUB;
|
||||
}
|
||||
if (strcmp(type_str, "MX_MAX") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_MAX;
|
||||
}
|
||||
if (strcmp(type_str, "MX_MIN") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_MIN;
|
||||
}
|
||||
if (strcmp(type_str, "MX_MUL") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_MUL;
|
||||
}
|
||||
if (strcmp(type_str, "MX_DIV") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_DIV;
|
||||
}
|
||||
if (strcmp(type_str, "MX_ABS") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_ABS;
|
||||
}
|
||||
if (strcmp(type_str, "MX_CMP") == 0)
|
||||
{
|
||||
return NODE_TYPE_MX_CMP;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -676,7 +732,8 @@ static int parse_xml_comb(tinyxml2::XMLElement *comb_el, LogicGraph *graph)
|
|||
{
|
||||
LogicLink &l = graph->links[graph->link_count++];
|
||||
memset(&l, 0, sizeof(l));
|
||||
l.dst_pin = -1;
|
||||
const char *pin_str_cb = sig->Attribute("pin");
|
||||
l.dst_pin = pin_str_cb ? atoi(pin_str_cb) : -1;
|
||||
l.src_type = NODE_TYPE_HW_INPUT;
|
||||
l.src_id = graph->nodes[si].id;
|
||||
l.dest_type = gn.type;
|
||||
|
|
@ -783,6 +840,18 @@ int parse_reclose_logic_xml(const char *file_path, LogicGraph graphs[], int *gra
|
|||
gn.timer_pending_output = -1;
|
||||
gn.type = gate_type_from_xml_str(gate->Attribute("type"));
|
||||
gn.id = gate->IntAttribute("id");
|
||||
|
||||
/* 读取延时毫秒 (62P/62D 链级 Gate) */
|
||||
const char *chain_delay = gate->Attribute("delayMs");
|
||||
|
||||
if (chain_delay)
|
||||
{
|
||||
gn.delay_ms = atoi(chain_delay);
|
||||
}
|
||||
else
|
||||
{
|
||||
gn.delay_ms = 1000;
|
||||
}
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement *outputs = chain->FirstChildElement("Outputs");
|
||||
|
|
@ -952,7 +1021,8 @@ int parse_reclose_logic_xml(const char *file_path, LogicGraph graphs[], int *gra
|
|||
{
|
||||
LogicLink &l = cur->links[cur->link_count++];
|
||||
memset(&l, 0, sizeof(l));
|
||||
l.dst_pin = -1;
|
||||
const char *pin_str_hc = sig->Attribute("pin");
|
||||
l.dst_pin = pin_str_hc ? atoi(pin_str_hc) : -1;
|
||||
l.src_type = NODE_TYPE_HW_INPUT;
|
||||
l.src_id = cur->nodes[si].id;
|
||||
l.dest_type = cur->nodes[cgi].type;
|
||||
|
|
@ -1139,9 +1209,9 @@ static int collect_input_signals(LogicGraph *graph, LogicNode *element_node,
|
|||
// SR/RS: 按 dst_pin 填入对应位置
|
||||
int is_srrs = (element_node->type == NODE_TYPE_SR_LATCH ||
|
||||
element_node->type == NODE_TYPE_RS_LATCH);
|
||||
if (is_srrs && link->dst_pin >= 0)
|
||||
if (link->dst_pin >= 0)
|
||||
{
|
||||
if (input_count < 2)
|
||||
if (link->dst_pin < max_inputs)
|
||||
{
|
||||
input_values[link->dst_pin] = src_node->value;
|
||||
if (link->dst_pin >= input_count)
|
||||
|
|
@ -1342,6 +1412,121 @@ static void execute_rs_latch(LogicNode *node, int *input_values, int input_count
|
|||
}
|
||||
}
|
||||
|
||||
static void execute_mx_add_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
int b = (input_count >= 2) ? input_values[1] : 0;
|
||||
node->value = a + b;
|
||||
if (input_count >= 2)
|
||||
{
|
||||
PLC_DBG(" 加法元件:%d + %d = %d\n", a, b, node->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
PLC_DBG(" 加法元件:输入不足(%d),输出=%d\n", input_count, node->value);
|
||||
}
|
||||
}
|
||||
static void execute_mx_sub_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
int b = (input_count >= 2) ? input_values[1] : 0;
|
||||
node->value = a - b;
|
||||
if (input_count >= 2)
|
||||
{
|
||||
PLC_DBG(" 减法元件:%d - %d = %d\n", a, b, node->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
PLC_DBG(" 减法元件:输入不足(%d),输出=%d\n", input_count, node->value);
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_mx_max_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
int b = (input_count >= 2) ? input_values[1] : 0;
|
||||
node->value = (a > b) ? a : b;
|
||||
if (input_count >= 2)
|
||||
{
|
||||
PLC_DBG(" 最大值元件:max(%d, %d) = %d\n", a, b, node->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
PLC_DBG(" 最大值元件:输入不足(%d),输出=%d\n", input_count, node->value);
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_mx_min_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
int b = (input_count >= 2) ? input_values[1] : 0;
|
||||
node->value = (a < b) ? a : b;
|
||||
if (input_count >= 2)
|
||||
{
|
||||
PLC_DBG(" 最小值元件:min(%d, %d) = %d\n", a, b, node->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
PLC_DBG(" 最小值元件:输入不足(%d),输出=%d\n", input_count, node->value);
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_mx_mul_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
int b = (input_count >= 2) ? input_values[1] : 0;
|
||||
node->value = a * b;
|
||||
if (input_count >= 2)
|
||||
{
|
||||
PLC_DBG(" 乘法元件:%d * %d = %d\n", a, b, node->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
PLC_DBG(" 乘法元件:输入不足(%d),输出=%d\n", input_count, node->value);
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_mx_div_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
int b = (input_count >= 2) ? input_values[1] : 0;
|
||||
if (b == 0)
|
||||
{
|
||||
node->value = 0;
|
||||
PLC_DBG(" 除法元件:%d / %d,除数为0,输出=0\n", a, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->value = a / b;
|
||||
PLC_DBG(" 除法元件:%d / %d = %d\n", a, b, node->value);
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_mx_abs_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
node->value = (a < 0) ? -a : a;
|
||||
PLC_DBG(" 绝对值元件:|%d| = %d\n", a, node->value);
|
||||
|
||||
}
|
||||
|
||||
static void execute_mx_cmp_gate(LogicNode *node, int *input_values, int input_count)
|
||||
{
|
||||
int a = (input_count >= 1) ? input_values[0] : 0;
|
||||
int b = (input_count >= 2) ? input_values[1] : 0;
|
||||
node->value = (a > b) ? 1 : 0;
|
||||
if (input_count >= 2)
|
||||
{
|
||||
PLC_DBG(" 比较元件:%d > %d → %d\n", a, b, node->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
PLC_DBG(" 比较元件:输入不足(%d),输出=%d\n", input_count, node->value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* ==================================================================
|
||||
* 拓扑排序 (Kahn 算法)
|
||||
* ================================================================== */
|
||||
|
|
@ -1463,6 +1648,15 @@ int execute_logic_graph(LogicGraph *graph, HardwareManager *hw)
|
|||
case NODE_TYPE_SR_LATCH: type_name = "SR触发器"; break;
|
||||
case NODE_TYPE_RS_LATCH: type_name = "RS触发器"; break;
|
||||
case NODE_TYPE_FALLING_EDGE: type_name = "下降沿元件"; break;
|
||||
case NODE_TYPE_TOGGLE: type_name = "翻转元件"; break;
|
||||
case NODE_TYPE_MX_ADD: type_name = "遥测加法元件"; break;
|
||||
case NODE_TYPE_MX_SUB: type_name = "遥测减法元件"; break;
|
||||
case NODE_TYPE_MX_MAX: type_name = "遥测最大值元件"; break;
|
||||
case NODE_TYPE_MX_MIN: type_name = "遥测最小值元件"; break;
|
||||
case NODE_TYPE_MX_MUL: type_name = "遥测乘法元件"; break;
|
||||
case NODE_TYPE_MX_DIV: type_name = "遥测除法元件"; break;
|
||||
case NODE_TYPE_MX_ABS: type_name = "遥测绝对值元件"; break;
|
||||
case NODE_TYPE_MX_CMP: type_name = "遥测比较元件"; break;
|
||||
default: type_name = "未知节点"; break;
|
||||
}
|
||||
PLC_DBG(" %s (类型=%d, ID=%d): 初始值=%d\n",
|
||||
|
|
@ -1553,7 +1747,49 @@ int execute_logic_graph(LogicGraph *graph, HardwareManager *hw)
|
|||
PLC_DBG("\n 处理RS触发器 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
execute_rs_latch(node, input_values, input_count);
|
||||
break;
|
||||
break;
|
||||
case NODE_TYPE_MX_ADD:
|
||||
PLC_DBG("\n 处理遥测加法元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
execute_mx_add_gate(node, input_values, input_count);
|
||||
break;
|
||||
case NODE_TYPE_MX_SUB:
|
||||
PLC_DBG("\n 处理遥测减法元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
execute_mx_sub_gate(node, input_values, input_count);
|
||||
break;
|
||||
case NODE_TYPE_MX_MAX:
|
||||
PLC_DBG("\n 处理遥测最大值元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
execute_mx_max_gate(node, input_values, input_count);
|
||||
break;
|
||||
case NODE_TYPE_MX_MIN:
|
||||
PLC_DBG("\n 处理遥测最小值元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
execute_mx_min_gate(node, input_values, input_count);
|
||||
break;
|
||||
case NODE_TYPE_MX_MUL:
|
||||
PLC_DBG("\n 处理遥测乘法元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
execute_mx_mul_gate(node, input_values, input_count);
|
||||
break;
|
||||
case NODE_TYPE_MX_DIV:
|
||||
PLC_DBG("\n 处理遥测除法元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
execute_mx_div_gate(node, input_values, input_count);
|
||||
break;
|
||||
case NODE_TYPE_MX_ABS:
|
||||
PLC_DBG("\n 处理遥测绝对值元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
if (input_count > 0) execute_mx_abs_gate(node, input_values, input_count);
|
||||
else { PLC_DBG(" 警告:绝对值元件没有输入信号\n"); node->value = 0; }
|
||||
break;
|
||||
case NODE_TYPE_MX_CMP:
|
||||
PLC_DBG("\n 处理遥测比较元件 (ID=%d)\n", node->id);
|
||||
input_count = collect_input_signals(graph, node, input_values, 10);
|
||||
if (input_count > 0) execute_mx_cmp_gate(node, input_values, input_count);
|
||||
else { PLC_DBG(" 警告:比较元件没有输入信号\n"); node->value = 0; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1600,6 +1836,15 @@ int execute_logic_graph(LogicGraph *graph, HardwareManager *hw)
|
|||
case NODE_TYPE_SR_LATCH: type_name = "SR触发器"; break;
|
||||
case NODE_TYPE_RS_LATCH: type_name = "RS触发器"; break;
|
||||
case NODE_TYPE_FALLING_EDGE: type_name = "下降沿元件"; break;
|
||||
case NODE_TYPE_TOGGLE: type_name = "翻转元件"; break;
|
||||
case NODE_TYPE_MX_ADD: type_name = "遥测加法元件"; break;
|
||||
case NODE_TYPE_MX_SUB: type_name = "遥测减法元件"; break;
|
||||
case NODE_TYPE_MX_MAX: type_name = "遥测最大值元件"; break;
|
||||
case NODE_TYPE_MX_MIN: type_name = "遥测最小值元件"; break;
|
||||
case NODE_TYPE_MX_MUL: type_name = "遥测乘法元件"; break;
|
||||
case NODE_TYPE_MX_DIV: type_name = "遥测除法元件"; break;
|
||||
case NODE_TYPE_MX_ABS: type_name = "遥测绝对值元件"; break;
|
||||
case NODE_TYPE_MX_CMP: type_name = "遥测比较元件"; break;
|
||||
default: type_name = "未知节点"; break;
|
||||
}
|
||||
PLC_DBG(" %s (ID=%d): 最终值=%d\n", type_name, node->id, node->value);
|
||||
|
|
@ -1754,7 +1999,7 @@ int app_plc_init1(void *arg)
|
|||
int ret = 0;
|
||||
ret |= dc_signal_out("plc.run_cnt", "plc线程计数", DATA_TYPE_U32, &p_app->run_cnt, "plc");
|
||||
|
||||
for (int i = 0; i < MAX_OUT_NODES; i++)
|
||||
for (int i = 0; i < MAX_OUT_ST_NODES; i++)
|
||||
{
|
||||
char saddr[64];
|
||||
char desc[64];
|
||||
|
|
@ -1763,6 +2008,15 @@ int app_plc_init1(void *arg)
|
|||
ret |= dc_signal_out(saddr, desc, DATA_TYPE_U8, &g_plc_st_out[i], "plc");
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_OUT_MX_NODES; i++)
|
||||
{
|
||||
char saddr[64];
|
||||
char desc[64];
|
||||
snprintf(saddr, sizeof(saddr), "plc.mx.out.%d", i);
|
||||
snprintf(desc, sizeof(desc), "plc遥测输出%d", i);
|
||||
ret |= dc_signal_out(saddr, desc, DATA_TYPE_F32, &g_plc_mx_out[i], "plc");
|
||||
}
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
LOG_E("app_plc_init1 dc_signal_out failed");
|
||||
|
|
|
|||
|
|
@ -509,6 +509,20 @@ void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
|
|||
sub++;
|
||||
}
|
||||
|
||||
/* 当注册 out/all 时,保存 datacenter out 信号到 XML */
|
||||
if (0 == strcmp(sub, "out") || 0 == strncmp(sub, "out ", 4)
|
||||
|| 0 == strcmp(sub, "all") || 0 == strncmp(sub, "all ", 4))
|
||||
{
|
||||
char resolved[512];
|
||||
char proc_dir[256] = {0};
|
||||
|
||||
if (0 == func_proc_self_dir(proc_dir, sizeof(proc_dir)))
|
||||
{
|
||||
snprintf(resolved, sizeof(resolved), "%s/config/SYSTEM/datacenter_out.xml", proc_dir);
|
||||
dc_save_out_signals_xml(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
/* 构建信号元数据 JSON */
|
||||
const char *dc_types[4];
|
||||
int dc_type_count = 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue