288 lines
12 KiB
HTML
288 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>XTU WEB DEBUG - Signal Monitor</title>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
|
||
.container { max-width: 1400px; margin: 0 auto; }
|
||
h1 { color: #333; text-align: center; }
|
||
.card { background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||
input, select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
|
||
.saddr-input { width: 280px; }
|
||
select { width: 140px; }
|
||
button { padding: 8px 16px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
||
button.secondary { background: #2196F3; }
|
||
#status { padding: 6px 12px; border-radius: 20px; font-weight: bold; }
|
||
.status-connected { background: #4CAF50; color: white; }
|
||
.status-disconnected { background: #f44336; color: white; }
|
||
#log { background: #f8f9fa; height: 150px; padding: 10px; overflow-y: auto; font-family: monospace; font-size: 12px; }
|
||
.log-entry { margin: 4px 0; }
|
||
.log-receive { color: #2196F3; }
|
||
.log-send { color: #4CAF50; }
|
||
.log-error { color: #f44336; }
|
||
|
||
/* 表格容器:固定高度 + 滚动条,最多显示8行 */
|
||
.table-container { margin-bottom: 20px; }
|
||
.table-title { font-size: 16px; font-weight: bold; margin-bottom: 8px; color: #2c3e50; }
|
||
.table-wrapper {
|
||
max-height: 320px; /* 刚好显示8行 */
|
||
overflow-y: auto; /* 超出自动滚动 */
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
}
|
||
.signalTable { width: 100%; border-collapse: collapse; background: white; }
|
||
.signalTable th {
|
||
position: sticky;
|
||
top: 0;
|
||
background: #2c3e50;
|
||
color: white;
|
||
padding: 12px;
|
||
text-align: left;
|
||
z-index: 10;
|
||
}
|
||
.signalTable td { padding: 10px; border-bottom: 1px solid #eee; }
|
||
.signalTable tr:hover { background: #f5f5f5; }
|
||
|
||
.form-row { display: flex; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
|
||
.form-group { display: flex; flex-direction: column; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>XTU WEB DEBUG - Signal Monitor</h1>
|
||
|
||
<div class="card">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>saddr *</label>
|
||
<input id="saddr" class="saddr-input" placeholder="iec.run_cnt">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>signal_type *</label>
|
||
<select id="signal_type">
|
||
<option value="out">out</option>
|
||
<option value="in">in</option>
|
||
<option value="yk">yk</option>
|
||
<option value="ao">ao</option>
|
||
<option value="param">param</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>curd *</label>
|
||
<select id="curd">
|
||
<option value="add">add</option>
|
||
<option value="set">set</option>
|
||
<option value="del">del</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>signal_data</label>
|
||
<input id="signal_data" placeholder="(can empty)">
|
||
</div>
|
||
</div>
|
||
<button onclick="sendSignal()">Send</button>
|
||
<button onclick="clearForm()" class="secondary">Clear Form</button>
|
||
<button onclick="clearAllTables()" class="secondary">Clear All Tables</button>
|
||
</div>
|
||
|
||
<div class="card">
|
||
WebSocket Status: <span id="status" class="status-disconnected">Disconnected</span>
|
||
<button onclick="connectWS()" class="secondary">Connect</button>
|
||
</div>
|
||
|
||
<!-- 4个独立信号表格 + 滚动容器 -->
|
||
<div class="card">
|
||
<div class="table-container">
|
||
<div class="table-title">OUT 信号</div>
|
||
<div class="table-wrapper">
|
||
<table class="signalTable">
|
||
<thead><tr><th>#</th><th>saddr</th><th>desc</th><th>数据类型</th><th>val</th></tr></thead>
|
||
<tbody id="outBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<div class="table-title">IN 信号</div>
|
||
<div class="table-wrapper">
|
||
<table class="signalTable">
|
||
<thead><tr><th>#</th><th>saddr</th><th>desc</th><th>数据类型</th><th>val</th></tr></thead>
|
||
<tbody id="inBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<div class="table-title">YK 信号</div>
|
||
<div class="table-wrapper">
|
||
<table class="signalTable">
|
||
<thead><tr><th>#</th><th>saddr</th><th>desc</th><th>数据类型</th><th>val</th><th>ctrl_type</th></tr></thead>
|
||
<tbody id="ykBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<div class="table-title">AO 信号</div>
|
||
<div class="table-wrapper">
|
||
<table class="signalTable">
|
||
<thead><tr><th>#</th><th>saddr</th><th>desc</th><th>数据类型</th><th>val</th><th>ctrl_type</th><th>min</th><th>max</th><th>step</th><th>unit</th><th>default</th></tr></thead>
|
||
<tbody id="aoBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<div class="table-title">PARAM 信号</div>
|
||
<div class="table-wrapper">
|
||
<table class="signalTable">
|
||
<thead><tr><th>#</th><th>saddr</th><th>desc</th><th>数据类型</th><th>val</th><th>ctrl_type</th><th>min</th><th>max</th><th>step</th><th>unit</th><th>default</th></tr></thead>
|
||
<tbody id="paramBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div id="log"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let ws = null;
|
||
const tableBodies = {
|
||
out: document.getElementById('outBody'),
|
||
in: document.getElementById('inBody'),
|
||
yk: document.getElementById('ykBody'),
|
||
ao: document.getElementById('aoBody'),
|
||
param: document.getElementById('paramBody')
|
||
};
|
||
|
||
// 控制类型数字转文字映射
|
||
const ctrlTypeMap = {
|
||
0: "只读",
|
||
1: "直控",
|
||
2: "选控"
|
||
};
|
||
|
||
const log = document.getElementById('log');
|
||
const status = document.getElementById('status');
|
||
|
||
function addLog(msg, type = 'info') {
|
||
const t = new Date().toLocaleTimeString();
|
||
log.innerHTML += `<div class="log-entry"><span>[${t}]</span> <span class="log-${type}">${msg}</span></div>`;
|
||
log.scrollTop = log.scrollHeight;
|
||
}
|
||
|
||
function connectWS() {
|
||
if (ws) return;
|
||
const url = `${location.protocol === 'https:' ? 'wss:' : 'ws:'}//${location.host}/ws`;
|
||
ws = new WebSocket(url);
|
||
ws.onopen = () => { status.textContent = 'Connected'; status.className = 'status-connected'; addLog('connected', 'info'); };
|
||
ws.onclose = () => { status.textContent = 'Disconnected'; status.className = 'status-disconnected'; ws = null; setTimeout(connectWS, 3000); };
|
||
ws.onerror = () => addLog('error', 'error');
|
||
ws.onmessage = (e) => {
|
||
try {
|
||
const data = JSON.parse(e.data);
|
||
addLog("received data", "receive");
|
||
parseData(data);
|
||
} catch (e) {
|
||
addLog("parse data error: " + e.message, "error");
|
||
}
|
||
};
|
||
}
|
||
|
||
// 每次收到数据,直接清空对应表格,重新渲染
|
||
function parseData(root) {
|
||
const types = ['out', 'in', 'yk', 'ao', 'param'];
|
||
types.forEach(type => {
|
||
const arr = root[type];
|
||
const body = tableBodies[type];
|
||
// 每次先清空表格
|
||
body.innerHTML = '';
|
||
if (Array.isArray(arr)) {
|
||
arr.forEach((item, index) => {
|
||
if (item.saddr) {
|
||
addRow(type, item, index + 1);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// 转换控制类型
|
||
function getCtrlText(val){
|
||
if(val === undefined || val === null) return "-";
|
||
return ctrlTypeMap[val] || val;
|
||
}
|
||
|
||
// 新增行
|
||
function addRow(type, item, index) {
|
||
const body = tableBodies[type];
|
||
const tr = body.insertRow();
|
||
let html = `
|
||
<td>${index}</td>
|
||
<td>${item.saddr}</td>
|
||
<td>${item.desc || '-'}</td>
|
||
<td>${item.type || '-'}</td>
|
||
<td>${item.val || '-'}</td>
|
||
`;
|
||
// yk 增加控制权限文字
|
||
if (type === 'yk') {
|
||
html += `<td>${getCtrlText(item.ctrl_type)}</td>`;
|
||
}
|
||
|
||
// ao 增加完整参数列
|
||
if (type === 'ao') {
|
||
html += `
|
||
<td>${getCtrlText(item.ctrl_type)}</td>
|
||
<td>${item.min || '-'}</td>
|
||
<td>${item.max || '-'}</td>
|
||
<td>${item.step || '-'}</td>
|
||
<td>${item.unit || '-'}</td>
|
||
<td>${item.default || '-'}</td>
|
||
`;
|
||
}
|
||
|
||
// param 增加完整参数列
|
||
if (type === 'param') {
|
||
html += `
|
||
<td>${getCtrlText(item.ctrl_type)}</td>
|
||
<td>${item.min || '-'}</td>
|
||
<td>${item.max || '-'}</td>
|
||
<td>${item.step || '-'}</td>
|
||
<td>${item.unit || '-'}</td>
|
||
<td>${item.default || '-'}</td>
|
||
`;
|
||
}
|
||
tr.innerHTML = html;
|
||
}
|
||
|
||
function sendSignal() {
|
||
const saddr = document.getElementById('saddr').value.trim();
|
||
const type = document.getElementById('signal_type').value;
|
||
const curd = document.getElementById('curd').value;
|
||
const data = document.getElementById('signal_data').value.trim();
|
||
if (!saddr) return addLog('saddr required', 'error');
|
||
if (!ws || ws.readyState !== 1) return addLog('not connected', 'error');
|
||
const msg = { saddr, signal_type: type, curd, signal_data: data };
|
||
ws.send(JSON.stringify(msg));
|
||
addLog(`sent: ${JSON.stringify(msg)}`, 'send');
|
||
}
|
||
|
||
function clearForm() {
|
||
document.getElementById('saddr').value = '';
|
||
document.getElementById('signal_data').value = '';
|
||
}
|
||
|
||
function clearAllTables() {
|
||
Object.values(tableBodies).forEach(body => body.innerHTML = '');
|
||
addLog('all tables cleared', 'info');
|
||
}
|
||
|
||
connectWS();
|
||
</script>
|
||
</body>
|
||
</html> |