// 测试操作页 — 间隔/超时机制 let autoRunning = false; let autoTotal = 0; let autoDone = 0; let autoFailed = 0; let autoRemaining = 0; let autoStartTime = ""; let localSinceStr = ""; let currentTestMode = null; // 0=灵敏度, 1=波动, null=未加载 let pollInterval = null; let nextCmdTimer = null; // 间隔等待定时器 let timeoutTimer = null; // 超时定时器 let timeoutAt = 0; // 超时时刻 (Date.now() + timeoutMs) let lastDoneCount = 0; // 上一轮 done 数,检测新响应 let cmdSentAt = 0; // 最近一次发送 0xB0 时间 let intervalMs = 10000; // 默认 10s let timeoutMs = 5000; // 默认 5s // ─── 手动指令 ───────────────────────────────── async function sendCmd(cmd) { try { const resp = await fetch("/api/command", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ dnt_id: DNT_ID, cmd }), }); const data = await resp.json(); if (data.ok) { console.log(`指令 ${cmd} 已下发: ${data.send_pkg}`); } } catch (e) { alert("发送失败: " + e.message); } } // ─── 自动化 ──────────────────────────────────── async function toggleAuto() { if (!autoRunning) { await startAuto(); } else { stopAuto(); } } async function startAuto() { const count = parseInt(document.getElementById("test-count").value) || 10; if (count < 1) return; // 读取参数 intervalMs = (parseFloat(document.getElementById("interval-sec").value) || 3) * 1000; timeoutMs = (parseFloat(document.getElementById("timeout-sec").value) || 10) * 1000; if (timeoutMs < 1000) timeoutMs = 1000; // 重置 autoRunning = true; autoTotal = count; autoDone = 0; autoFailed = 0; autoRemaining = count; lastDoneCount = 0; autoStartTime = new Date().toISOString(); const now = new Date(); localSinceStr = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0") + " " + String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0") + ":" + String(now.getSeconds()).padStart(2, "0"); // 显示开始时间 document.getElementById("auto-time").style.display = "block"; document.getElementById("time-start").textContent = new Date().toLocaleString(); document.getElementById("time-end").textContent = "-"; // 清空显示 resetAverages(); document.getElementById("latest-result").innerHTML = '

等待测试...

'; document.getElementById("latest-wave").innerHTML = '

暂无波动数据...

'; document.getElementById("progress-bar").style.width = "0%"; document.getElementById("progress-text").textContent = "0/" + count + " (0 失败)"; document.getElementById("stat-done").textContent = "0"; document.getElementById("stat-failed").textContent = "0"; document.getElementById("stat-remaining").textContent = count; document.getElementById("auto-status").textContent = ""; document.querySelector("#records-table tbody").innerHTML = ""; document.getElementById("records-empty").style.display = "block"; const btn = document.getElementById("btn-auto"); btn.textContent = "结束"; btn.className = "btn-stop"; // 清除旧记录 + 插入第一条 try { const resp = await fetch("/api/automation/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ dnt_id: DNT_ID, count }), }); const data = await resp.json(); if (data.ok) { cmdSentAt = Date.now(); timeoutAt = cmdSentAt + timeoutMs; armTimeout(); setStatus(`已发送,超时 ${timeoutMs/1000}s...`); } } catch (e) { console.error("启动失败:", e); stopAuto(); } // 启动轮询 pollInterval = setInterval(pollProgress, 500); } function stopAuto() { autoRunning = false; clearInterval(pollInterval); pollInterval = null; clearTimeout(nextCmdTimer); nextCmdTimer = null; clearTimeout(timeoutTimer); timeoutTimer = null; document.getElementById("auto-status").textContent = ""; document.getElementById("time-end").textContent = new Date().toLocaleString(); const btn = document.getElementById("btn-auto"); btn.textContent = "开始"; btn.className = "btn-start"; } // ─── 发送下一条 0xB0 ──────────────────────────── async function sendNextCmd() { if (!autoRunning) return; if (autoRemaining <= 0) { // 检查是否还有等待完成的 if (autoDone + autoFailed >= autoTotal) { stopAuto(); return; } } try { const r = await fetch("/api/command", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ dnt_id: DNT_ID, cmd: "B0" }), }); const rd = await r.json(); if (rd.ok) { cmdSentAt = Date.now(); timeoutAt = cmdSentAt + timeoutMs; armTimeout(); setStatus(`已发送,超时 ${timeoutMs/1000}s...`); } } catch (e) { console.error("发送下一条失败:", e); } } // ─── 超时定时器 ───────────────────────────────── function armTimeout() { clearTimeout(timeoutTimer); timeoutTimer = setTimeout(onTimeout, timeoutMs + 500); // +500ms 容差 } function onTimeout() { if (!autoRunning) return; // 超时:即使没收到回复也计数失败,立即发下一条 autoFailed++; autoRemaining = autoTotal - autoDone - autoFailed; if (autoRemaining < 0) autoRemaining = 0; updateUI(); setStatus(`超时!立即发下一条...`); console.log(`超时 (${timeoutMs}ms),失败数: ${autoFailed}`); clearTimeout(nextCmdTimer); nextCmdTimer = null; if (autoRemaining > 0) { sendNextCmd(); } else { stopAuto(); } } // ─── 轮询 ────────────────────────────────────── async function pollProgress() { if (!autoRunning) return; try { const resp = await fetch(`/api/automation/${DNT_ID}/progress?since=${encodeURIComponent(localSinceStr)}`); const data = await resp.json(); const stats = data.stats; // ── 先渲染数据(放在所有 return 之前,避免完成时跳过渲染)── try { if (data.latest) renderLatest(data.latest); } catch (e) { console.error("renderLatest:", e); } try { if (data.averages) renderAverages(data.averages); } catch (e) { console.error("renderAverages:", e); } try { if (data.latest_wave) renderLatestWave(data.latest_wave); } catch (e) { console.error("renderLatestWave:", e); } try { if (data.records && data.records.length) renderRecords(data.records); } catch (e) { console.error("renderRecords:", e); } // 更新计数 const newDone = stats.done || 0; const newFailed = stats.failed || 0; if (newDone > lastDoneCount) { // 收到新回复 → 清除超时,开始间隔等待 const delta = newDone - lastDoneCount; lastDoneCount = newDone; autoDone = newDone; autoFailed = newFailed; autoRemaining = autoTotal - autoDone - autoFailed; if (autoRemaining < 0) autoRemaining = 0; clearTimeout(timeoutTimer); timeoutTimer = null; clearTimeout(nextCmdTimer); nextCmdTimer = null; updateUI(); if (autoRemaining > 0) { const wait = (intervalMs / 1000).toFixed(0); setStatus(`收到 ${delta} 条回复,等待 ${wait}s...`); nextCmdTimer = setTimeout(() => { setStatus("发送中..."); sendNextCmd(); }, intervalMs); } else { setStatus("全部完成"); stopAuto(); return; } } else { // 没有新回复,更新超时计数 autoFailed = newFailed; autoRemaining = autoTotal - autoDone - autoFailed; if (autoRemaining < 0) autoRemaining = 0; // 检查是否全部完成 if (autoRemaining <= 0 && autoDone + autoFailed >= autoTotal) { stopAuto(); return; } } } catch (e) { console.error("轮询失败:", e); } } // ─── 页面加载时获取初始数据 ────────────────────── async function loadTestMode() { try { const resp = await fetch(`/api/fixture/param/${DNT_ID}`); const param = await resp.json(); if (param && param.dnt_id) { updateTestModeUI(param.TestMode); } else { // 没有工装参数时,尝试从最新测试数据获取 const r2 = await fetch(`/api/automation/${DNT_ID}/progress`); const d2 = await r2.json(); if (d2.latest && d2.latest.test_mode !== undefined) { updateTestModeUI(d2.latest.test_mode); } } } catch (e) { /* 静默 */ } } function updateTestModeUI(mode) { currentTestMode = mode; const indicator = document.getElementById("test-mode-indicator"); const waveSection = document.getElementById("wave-section"); if (mode === 1) { indicator.innerHTML = '当前测试模式:波动测试'; waveSection.style.display = ''; } else { indicator.innerHTML = '当前测试模式:灵敏度测试'; waveSection.style.display = 'none'; } } async function loadInitialData() { await loadTestMode(); refreshDeviceStatus(); try { const resp = await fetch(`/api/automation/${DNT_ID}/progress`); const data = await resp.json(); if (data.latest) renderLatest(data.latest); if (data.latest_wave) renderLatestWave(data.latest_wave); } catch (e) { // 初始加载静默失败 } } loadInitialData(); // ─── 设备状态异步刷新 ────────────────────────── async function refreshDeviceStatus() { try { const resp = await fetch(`/api/devices/${DNT_ID}/status`); const data = await resp.json(); if (data.ok) { const el = document.getElementById("device-status-text"); if (el) { el.textContent = data.state_name; if (data.state === 1) { el.className = "status-online"; } else if (data.state === 2) { el.className = "status-poor"; } else { el.className = "status-offline"; } } } } catch (e) { // 静默失败 } } // 每 5 秒刷新设备状态 setInterval(refreshDeviceStatus, 5000); // ─── UI ──────────────────────────────────────── function setStatus(msg) { document.getElementById("auto-status").textContent = msg; } function updateUI() { document.getElementById("stat-done").textContent = autoDone; document.getElementById("stat-failed").textContent = autoFailed; document.getElementById("stat-remaining").textContent = autoRemaining; const total = autoTotal || 1; const pct = Math.round((autoDone / total) * 100); document.getElementById("progress-bar").style.width = pct + "%"; document.getElementById("progress-text").textContent = `${autoDone}/${autoTotal} (${autoFailed} 失败)`; } function toSpeed(v) { if (v === null || v === undefined || v === '') return '-'; return (parseFloat(v) / 10).toFixed(1); } function fmtTime(v) { if (!v) return '-'; // Flask jsonify 给 MySQL DATETIME 加 "GMT" 后缀,但实际值是服务器本地时间(UTC+8) // 去掉 "GMT" 让 JS 按本地时间解析,避免时区偏移 8 小时 const cleaned = String(v).replace(/ GMT$/, ''); const d = new Date(cleaned); if (isNaN(d.getTime())) return String(v).substring(0, 19); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const d2 = String(d.getDate()).padStart(2, '0'); const h = String(d.getHours()).padStart(2, '0'); const min = String(d.getMinutes()).padStart(2, '0'); const s = String(d.getSeconds()).padStart(2, '0'); return `${y}-${m}-${d2} ${h}:${min}:${s}`; } const RELAY_MAP = { 0: '无输出', 1: '存在信号', 2: '脉冲信号', 3: '存在信号; 脉冲信号', }; function decodeRelay(v) { if (v === null || v === undefined || v === '') return '-'; return RELAY_MAP[parseInt(v)] || `0x${parseInt(v).toString(16).toUpperCase().padStart(2, '0')}`; } // ─── 显示最新结果 ────────────────────────────── function renderLatest(data) { if (data.test_mode !== undefined && data.test_mode !== currentTestMode) { updateTestModeUI(data.test_mode); } const div = document.getElementById("latest-result"); div.innerHTML = `

设备型号:${data.str_type || '-'}

测试模式:${data.test_mode === 1 ? '波动测试' : '灵敏度测试'}

峰峰值:${data.ppvalue?.toFixed(2) || '-'} V

开始工作频率:${data.idle_freq || '-'} Hz

进入工作频率:${data.enter_freq || '-'} Hz

离开工作频率:${data.exit_freq || '-'} Hz

进入距离:${data.enter_dist || '-'} mm

离开距离:${data.exit_dist || '-'} mm

进入速度:${toSpeed(data.enter_speed)} m/s

离开速度:${toSpeed(data.exit_speed)} m/s

是否完成:${data.iffinish === '1' ? '是' : '否'}

故障信息:${data.fault_info || '无'}

继电器:${decodeRelay(data.relay_code)}

时间:${fmtTime(data.create_time)}

`; } // ─── 显示平均值 ──────────────────────────────── function renderAverages(data) { document.getElementById("avg-ppvalue").textContent = data.avg_ppvalue || "-"; document.getElementById("avg-idle-freq").textContent = data.avg_idle_freq || "-"; document.getElementById("avg-enter-freq").textContent = data.avg_enter_freq || "-"; document.getElementById("avg-enter-dist").textContent = data.avg_enter_dist || "-"; document.getElementById("avg-exit-dist").textContent = data.avg_exit_dist || "-"; document.getElementById("avg-enter-speed").textContent = data.avg_enter_speed || "-"; document.getElementById("avg-exit-speed").textContent = data.avg_exit_speed || "-"; } function resetAverages() { ["ppvalue", "idle-freq", "enter-freq", "enter-dist", "exit-dist", "enter-speed", "exit-speed"].forEach(id => { document.getElementById("avg-" + id).textContent = "-"; }); } // ─── 显示波动测试数据 ────────────────────────── function renderLatestWave(data) { const div = document.getElementById("latest-wave"); if (!data || !data.work_freq) { div.innerHTML = '

暂无波动数据...

'; return; } div.innerHTML = `

剩余次数:${data.remain_count || 0}

工作频率:${data.work_freq || '-'} Hz

当前距离:${data.curr_dist || '-'} mm

当前速度:${toSpeed(data.speed)} m/s

最近距离:${data.near_dist || '-'} mm

最远距离:${data.far_dist || '-'} mm

进入高度 (B4):${data.b4_enter_dist || '-'} mm

离开高度 (B4):${data.b4_leave_dist || '-'} mm

继电器:${decodeRelay(data.relay_code)}

时间:${fmtTime(data.create_time)}

`; } // ─── 显示本轮测试明细 ────────────────────────── function renderRecords(records) { if (!records || !records.length) { document.getElementById("records-empty").style.display = "block"; document.getElementById("records-table").style.display = "none"; return; } document.getElementById("records-empty").style.display = "none"; document.getElementById("records-table").style.display = ""; const tbody = document.querySelector("#records-table tbody"); tbody.innerHTML = records.map((r, i) => ` ${i + 1} ${r.sn_state === 2 ? 'OK' : r.sn_state === 3 ? '超时' : '?'} ${r.test_mode === 1 ? '波动' : '灵敏度'} ${r.ppvalue?.toFixed(2) || '-'} ${r.idle_freq || '-'} ${r.enter_dist || '-'} ${r.exit_dist || '-'} ${toSpeed(r.enter_speed)} ${fmtTime(r.create_time)} `).join(""); }