Files
vd_test_fixture/edc-web/app/static/js/fixture.js
wangfq ef796f6213 feat: DG430 V2.0.3 — 波动测试模式 前端+后端同步
edc_server:
  - dg430.py: 新增0xB4解析; 0x4C扩展6字段(向后兼容)
  - models.py: tb_fixture_param DDL + upsert 新增6字段
  - handlers.py: parse_loop 添加0xB4处理; 0x4C传参扩展

edc-web:
  - fixture.py: build_4b_packet() 新增6个波动参数
  - models.py: upsert_fixture_param 字段列表扩展
  - fixture.html: 新增波动测试参数输入区(6字段)
  - fixture.js: getFormParams/fillForm/saveToDb/sendConfig 全部扩展
2026-06-02 18:06:14 +08:00

357 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 工装配置页
let baseTests = []; // 所有车检器基准参数
let selectedBaseTest = null;
let pollTimer4C = null; // 0x4C 参数查询轮询
let pollTimers = {}; // record_id → timer (指令响应轮询)
// ─── 初始化 ─────────────────────────────────
async function init() {
await loadBaseTests();
await loadFixtureParam();
}
// ─── Toast 提示 ─────────────────────────────
function toast(msg, isError = false) {
const el = document.getElementById("toast");
el.textContent = msg;
el.className = "msg-toast " + (isError ? "error" : "") + " show";
clearTimeout(el._timeout);
el._timeout = setTimeout(() => { el.className = "msg-toast"; }, 3000);
}
// ─── 通信日志 ───────────────────────────────
function commLog(type, hex, detail) {
const el = document.getElementById("comm-log");
if (el.querySelector('div') && el.querySelector('div').textContent === '等待操作…') {
el.innerHTML = '';
}
const time = new Date().toLocaleTimeString();
const colors = { send: '#4ec9b0', recv: '#ce9178', ok: '#6a9955', fail: '#f44747', info: '#888' };
const labels = { send: '→ 发送', recv: '← 收到', ok: '✓ 成功', fail: '✗ 失败', info: '· 信息' };
const color = colors[type] || '#888';
const label = labels[type] || '';
const html = `<div style="margin-bottom:3px;">
<span style="color:#569cd6;">${time}</span>
<span style="color:${color}; font-weight:600;"> ${label}</span>
<span style="color:#aaa;"> ${detail || ''}</span>
${hex ? `<br><span style="color:#808080; padding-left:10px;">${hex}</span>` : ''}
</div>`;
el.insertAdjacentHTML('afterbegin', html);
}
// ─── 解析 Flag 响应 ──────────────────────────
const FLAG_CMDS = { '4B': '配置参数', '4D': '出厂初始化', '4E': '设备复位' };
function parseFlagResponse(rcvPkg, cmd) {
// 格式: 7F8102{CMD}{FLAG}{XOR}{SUM} → FLAG 在位置 8-9
if (!rcvPkg || rcvPkg.length < 10) return null;
const flag = parseInt(rcvPkg.substring(8, 10), 16);
return flag;
}
// ─── 加载车检器测试基准 ──────────────────────
async function loadBaseTests() {
const search = document.getElementById("ref-search").value;
try {
const resp = await fetch(`/api/vehicle-base-test?search=${encodeURIComponent(search)}`);
baseTests = await resp.json();
renderBaseTestTable();
populateDevTypeSelect();
} catch (e) {
console.error("加载基准参数失败:", e);
}
}
function renderBaseTestTable() {
const tbody = document.getElementById("ref-table-body");
if (!baseTests.length) {
tbody.innerHTML = '<tr><td colspan="5" style="color:#999;text-align:center;">暂无数据</td></tr>';
return;
}
tbody.innerHTML = baseTests.map(t => `
<tr onclick="selectBaseTest(${t.id})" data-id="${t.id}"
class="${selectedBaseTest && selectedBaseTest.id === t.id ? 'selected' : ''}">
<td>${t.type_num}</td>
<td>${esc(t.dev_name)}</td>
<td>${t.SensMin}~${t.SensMax}</td>
<td>${t.FreMin}~${t.FreMax}</td>
<td>${t.PeakMin}~${t.PeakMax}</td>
</tr>
`).join("");
}
function populateDevTypeSelect() {
const sel = document.getElementById("param-dev-type");
sel.innerHTML = '<option value="0">-- 请选择车检器基准 --</option>' +
baseTests.map(t => `<option value="${t.type_num}">${t.type_num} - ${esc(t.dev_name)}</option>`).join("");
if (selectedBaseTest) {
sel.value = selectedBaseTest.type_num;
}
}
function selectBaseTest(id) {
selectedBaseTest = baseTests.find(t => t.id === id);
if (!selectedBaseTest) return;
fillFromBaseTest(selectedBaseTest);
renderBaseTestTable();
}
function fillFromBaseTest(t) {
document.getElementById("param-dev-type").value = t.type_num;
document.getElementById("param-sens-min").value = t.SensMin;
document.getElementById("param-sens-max").value = t.SensMax;
document.getElementById("param-fre-min").value = t.FreMin;
document.getElementById("param-fre-max").value = t.FreMax;
document.getElementById("param-peak-min").value = t.PeakMin;
document.getElementById("param-peak-max").value = t.PeakMax;
}
function onDevTypeChange() {
const typeNum = parseInt(document.getElementById("param-dev-type").value);
const matched = baseTests.find(t => t.type_num === typeNum);
if (matched) selectBaseTest(matched.id);
else { selectedBaseTest = null; renderBaseTestTable(); }
}
// ─── 从 DB 加载/刷新/保存 ────────────────────
async function loadFixtureParam() {
try {
const resp = await fetch(`/api/fixture/param/${DNT_ID}`);
const param = await resp.json();
if (param && param.dnt_id) {
fillFormFromParam(param);
commLog('info', null, '已从数据库加载工装参数');
}
} catch (e) { console.error("加载工装参数失败:", e); }
}
function fillFormFromParam(param) {
document.getElementById("param-addr").value = param.Addr || 1;
document.getElementById("param-test-mode").value = param.TestMode || 0;
document.getElementById("param-reset-dis").value = param.RestDis || 0;
document.getElementById("param-minus-dis").value = param.MinusDis || 0;
document.getElementById("param-dev-type").value = param.DevType || 0;
document.getElementById("param-sens-min").value = param.SensMin || 0;
document.getElementById("param-sens-max").value = param.SensMax || 0;
document.getElementById("param-fre-min").value = param.FreMin || 0;
document.getElementById("param-fre-max").value = param.FreMax || 0;
document.getElementById("param-peak-min").value = param.PeakMin || 0;
document.getElementById("param-peak-max").value = param.PeakMax || 0;
document.getElementById("param-far-tol").value = param.FarTol || 0;
document.getElementById("param-near-tol").value = param.NearTol || 0;
document.getElementById("param-step-tol").value = param.StepTol || 0;
document.getElementById("param-back-forth").value = param.BackForth || 0;
document.getElementById("param-near-stay").value = param.NearStay || 0;
document.getElementById("param-far-stay").value = param.FarStay || 0;
const matched = baseTests.find(t => t.type_num === param.DevType);
if (matched) { selectedBaseTest = matched; renderBaseTestTable(); }
}
async function refreshParams() {
const param = await (await fetch(`/api/fixture/param/${DNT_ID}`)).json();
if (param && param.dnt_id) {
fillFormFromParam(param);
commLog('info', null, '已刷新:从数据库加载工装参数');
toast("已刷新");
} else {
toast("数据库中没有该工装的参数,请先查询(0x4C)或保存", true);
commLog('info', null, '刷新失败:数据库中没有参数');
}
}
async function saveToDb() {
const data = getFormParams();
try {
const resp = await fetch(`/api/fixture/param/${DNT_ID}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
Addr: data.addr, DevType: data.dev_type, TestMode: data.test_mode,
RestDis: data.reset_dis, MinusDis: data.minus_dis,
SensMin: data.sens_min, SensMax: data.sens_max,
FreMin: data.fre_min, FreMax: data.fre_max,
PeakMin: data.peak_min, PeakMax: data.peak_max,
FarTol: data.far_tol, NearTol: data.near_tol,
StepTol: data.step_tol, BackForth: data.back_forth,
NearStay: data.near_stay, FarStay: data.far_stay,
}),
});
const result = await resp.json();
if (result.ok) {
commLog('info', null, '参数已保存到数据库');
toast("已保存到数据库");
} else {
toast("保存失败: " + (result.error || ""), true);
}
} catch (e) { toast("保存失败: " + e.message, true); }
}
function getFormParams() {
return {
addr: parseInt(document.getElementById("param-addr").value) || 1,
dev_type: parseInt(document.getElementById("param-dev-type").value) || 0,
test_mode: parseInt(document.getElementById("param-test-mode").value) || 0,
reset_dis: parseInt(document.getElementById("param-reset-dis").value) || 0,
minus_dis: parseInt(document.getElementById("param-minus-dis").value) || 0,
sens_min: parseInt(document.getElementById("param-sens-min").value) || 0,
sens_max: parseInt(document.getElementById("param-sens-max").value) || 0,
fre_min: parseInt(document.getElementById("param-fre-min").value) || 0,
fre_max: parseInt(document.getElementById("param-fre-max").value) || 0,
peak_min: parseInt(document.getElementById("param-peak-min").value) || 0,
peak_max: parseInt(document.getElementById("param-peak-max").value) || 0,
far_tol: parseInt(document.getElementById("param-far-tol").value) || 0,
near_tol: parseInt(document.getElementById("param-near-tol").value) || 0,
step_tol: parseInt(document.getElementById("param-step-tol").value) || 0,
back_forth: parseInt(document.getElementById("param-back-forth").value) || 0,
near_stay: parseInt(document.getElementById("param-near-stay").value) || 0,
far_stay: parseInt(document.getElementById("param-far-stay").value) || 0,
};
}
// ─── 轮询 serialnet 响应(通用)─────────────
function startRespPolling(recordId, cmd) {
stopRespPolling(recordId);
let attempts = 0;
pollTimers[recordId] = setInterval(async () => {
attempts++;
try {
const resp = await fetch(`/api/fixture/serialnet/${recordId}`);
const rec = await resp.json();
if (rec.state === 2) {
// 已完成
stopRespPolling(recordId);
const rcvPkg = rec.rcv_pkg || '';
if (rcvPkg) {
commLog('recv', rcvPkg, '');
// 解析 Flag0x4B/0x4D/0x4E
if (cmd in FLAG_CMDS) {
const flag = parseFlagResponse(rcvPkg, cmd);
if (flag === 0) {
commLog('ok', null, `${FLAG_CMDS[cmd]} 成功`);
toast(`${FLAG_CMDS[cmd]} 成功`);
} else {
commLog('fail', null, `${FLAG_CMDS[cmd]} 失败 (Flag=${flag})`);
toast(`${FLAG_CMDS[cmd]} 失败`, true);
}
} else {
commLog('ok', null, `指令 0x${cmd} 已完成`);
toast(`指令 0x${cmd} 已完成`);
}
// 0x4C 返回后刷新参数
if (cmd === '4C') {
// 参数已在后端 upsert直接从 DB 加载
setTimeout(async () => {
const p = await (await fetch(`/api/fixture/param/${DNT_ID}`)).json();
if (p && p.dnt_id) fillFormFromParam(p);
}, 500);
}
} else {
commLog('info', null, `指令 0x${cmd} 已完成(无返回数据)`);
}
} else if (rec.state === 3) {
// 超时
stopRespPolling(recordId);
commLog('fail', null, `指令 0x${cmd} 超时(设备无响应)`);
toast(`指令 0x${cmd} 超时`, true);
}
// state=0/1 → 继续等待
} catch (e) {
// 忽略网络错误
}
if (attempts >= 4) {
stopRespPolling(recordId);
commLog('fail', null, `指令 0x${cmd} 轮询超时4秒无响应`);
}
}, 1000);
}
function stopRespPolling(recordId) {
if (pollTimers[recordId]) {
clearInterval(pollTimers[recordId]);
delete pollTimers[recordId];
}
}
// ─── 发送工装指令(通用)────────────────────
async function sendFixtureCmd(cmd) {
try {
const resp = await fetch("/api/fixture/command", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dnt_id: DNT_ID, cmd }),
});
const data = await resp.json();
if (data.ok) {
commLog('send', data.send_pkg, `${cmdName(cmd)} (0x${cmd})`);
toast(`指令 0x${cmd} 已下发`);
startRespPolling(data.record_id, cmd);
} else {
commLog('fail', null, `指令 0x${cmd} 发送失败: ${data.error}`);
toast(`失败: ${data.error}`, true);
}
} catch (e) {
commLog('fail', null, `发送 0x${cmd} 异常: ${e.message}`);
toast("发送失败: " + e.message, true);
}
}
// ─── 发送配置指令 0x4B ───────────────────────
async function sendConfig() {
const params = getFormParams();
try {
const resp = await fetch("/api/fixture/command", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dnt_id: DNT_ID, cmd: "4B", params }),
});
const data = await resp.json();
if (data.ok) {
commLog('send', data.send_pkg, `配置参数 (0x4B)`);
toast("配置指令 0x4B 已下发");
startRespPolling(data.record_id, "4B");
// 同时保存到数据库
await fetch(`/api/fixture/param/${DNT_ID}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
Addr: params.addr, DevType: params.dev_type, TestMode: params.test_mode,
RestDis: params.reset_dis, MinusDis: params.minus_dis,
SensMin: params.sens_min, SensMax: params.sens_max,
FreMin: params.fre_min, FreMax: params.fre_max,
PeakMin: params.peak_min, PeakMax: params.peak_max,
FarTol: params.far_tol, NearTol: params.near_tol,
StepTol: params.step_tol, BackForth: params.back_forth,
NearStay: params.near_stay, FarStay: params.far_stay,
}),
});
} else {
toast(`失败: ${data.error}`, true);
}
} catch (e) {
toast("配置失败: " + e.message, true);
}
}
function cmdName(cmd) {
const names = { '4A': '获取版本号', '4B': '配置参数', '4C': '查询参数', '4D': '出厂初始化', '4E': '设备复位' };
return names[cmd] || cmd;
}
function esc(s) { return (s || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }
init();