feat(motor): 序列每步新增绿色「单发」按钮,支持仅发送该步动作一次

This commit is contained in:
cbb
2026-09-07 22:18:50 +08:00
parent 3b156547bc
commit 4e018af168
5 changed files with 268 additions and 2 deletions
+56 -1
View File
@@ -1997,6 +1997,7 @@ const seq = {
round: 1, /* 当前轮(1 起) */
stepIdx: 0, /* 当前步索引 */
statusText: "未运行",
singleIdx: null, /* 正在单发的步骤索引,null = 无单发在途 */
};
const seqListEl = $("#seqList");
const seqStartBtn = $("#seqStartBtn");
@@ -2069,6 +2070,9 @@ function renderSeqList() {
<input class="seq-num seq-hold" data-f="holdMs" type="number" value="${s.holdMs}" min="0" max="600000" step="100" aria-label="步骤 ${i + 1} 保持时间 ms" />
<span class="seq-hold-unit">ms</span>
<div class="seq-ops">
<button data-op="sendOne" title="单发本步(仅发送这一步的全部电机动作,不推进序列)" class="seq-send-one">
<svg width="13" height="13" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M4.5 2.8 11 7l-6.5 4.2z" fill="currentColor"/></svg>
</button>
<button data-op="addAction" title="本步添加电机动作(复制最后一个动作)">
<svg width="13" height="13" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M7 2.5v9M2.5 7h9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</button>
@@ -2118,8 +2122,10 @@ function renderSeqList() {
function updateSeqUi() {
const running = seq.running;
const sendingIdx = seq.singleIdx;
seqStartBtn.textContent = running ? "■ 停止序列" : "▶ 开始序列";
seqStartBtn.classList.toggle("btn-primary", running);
seqStartBtn.disabled = !running && sendingIdx != null;
const st = $("#seqStatus");
if (st) {
if (running) {
@@ -2127,6 +2133,9 @@ function updateSeqUi() {
const cnt = cs ? cs.actions.length : 0;
st.textContent = `运行中 · 步骤 ${seq.stepIdx + 1}/${seq.steps.length} (${cnt} 电机) · 第 ${seq.round} 轮`;
st.classList.add("on");
} else if (sendingIdx != null) {
st.textContent = `单发步骤 ${sendingIdx + 1}…`;
st.classList.add("on");
} else {
st.textContent = seq.statusText || `未运行 · ${seq.steps.length} 步`;
st.classList.remove("on");
@@ -2134,7 +2143,9 @@ function updateSeqUi() {
}
seqListEl.classList.toggle("seq-locked", running);
seqListEl.querySelectorAll(".seq-step").forEach((row) => {
row.classList.toggle("current", running && parseInt(row.dataset.idx, 10) === seq.stepIdx);
const idx = parseInt(row.dataset.idx, 10);
row.classList.toggle("current", running && idx === seq.stepIdx);
row.classList.toggle("sending", !running && sendingIdx === idx);
});
seqLoopsInput.disabled = running;
$("#seqAddBtn").disabled = running;
@@ -2179,6 +2190,10 @@ seqListEl.addEventListener("click", (e) => {
const step = seq.steps[i];
if (!step) return;
const op = btn.dataset.op;
if (op === "sendOne") {
sendSeqStepOnce(i);
return;
}
if (op === "up" && i > 0) {
const t = seq.steps[i]; seq.steps[i] = seq.steps[i - 1]; seq.steps[i - 1] = t;
} else if (op === "down" && i < seq.steps.length - 1) {
@@ -2231,6 +2246,46 @@ seqStartBtn.addEventListener("click", () => {
startSeq();
});
/* 单发一步:只把该步的全部动作经一次 sendBurst 近同时下发,不循环、不推进步骤、不等 holdMs。
* 与序列运行互斥(运行中按钮被 .seq-locked 屏蔽),同一时刻只允许一个单发在途。 */
async function sendSeqStepOnce(i) {
if (seq.running || seq.singleIdx != null) return;
const step = seq.steps[i];
if (!step || !step.actions.length) return;
if (!state.connected) { toast("设备未连接", "err"); return; }
if (inBusCooldown()) return;
const chBad = step.actions.find((a) => !state.chEnabled[a.ch]);
if (chBad) { toast(`步骤 ${i + 1} 的通道 CH${chBad.ch} 已禁用`, "err"); return; }
seq.singleIdx = i;
updateSeqUi();
const targets = step.actions.map((a) => `CH${a.ch}·N${a.node}`).join(" ");
const time = new Date().toLocaleTimeString("zh-CN", { hour12: false });
$("#sbLastSend").textContent = `最近发送: 单发步${i + 1} → ${step.actions.length} 电机 [${targets}] ${time}`;
try {
const res = await sendBurst(step.actions);
if (res.busy) { toast("发送通道忙,请稍后重试", "warn"); return; }
if (!res.ok) { toast(`步骤 ${i + 1} 发送失败:${res.desc || "未知错误"}`, "err"); return; }
for (const a of step.actions) {
state.lastSentMode.set(a.node, parseInt(a.mode, 10) || 0);
state.lastSentTargets.set(a.node, {
mode: parseInt(a.mode, 10) || 0,
angle: parseFloat(a.angle) || 0,
velocity: parseFloat(a.velocity) || 0,
current: parseFloat(a.current) || 0,
voltage: parseFloat(a.voltage) || 0,
});
}
state.txCount++;
renderNodeList();
seq.statusText = `已单发步骤 ${i + 1}`;
toast(`步骤 ${i + 1} 已单发 · ${step.actions.length} 电机 [${targets}]`, "ok");
} finally {
seq.singleIdx = null;
updateSeqUi();
}
}
function startSeq() {
if (!state.connected) { toast("设备未连接", "err"); return; }
if (seq.steps.length === 0) { toast("请先添加至少一个步骤", "warn"); return; }