Co-authored-by: osiu97 <osiu97@gmail.com> Reviewed-on: #19
This commit was merged in pull request #19.
This commit is contained in:
+118
-70
@@ -1,6 +1,8 @@
|
||||
import BLE from './ble.js';
|
||||
import IK from './kinematics.js';
|
||||
import UI from './ui.js';
|
||||
import Settings from './settings.js';
|
||||
import { MotionEffects } from './motion-effects.js';
|
||||
|
||||
const CONFIG = {
|
||||
SEGMENT_SIZE_MM: 25, // Maximum distance between waypoints to prevent out-of-bounds joint-space arcs
|
||||
@@ -47,11 +49,17 @@ let motionQueue = [];
|
||||
let isMoving = false;
|
||||
let currentResolve = null;
|
||||
let queueCheckInterval = null;
|
||||
let currentMotionMode = 'direct';
|
||||
let hasHesitated = false;
|
||||
|
||||
async function executeQueue() {
|
||||
if (motionQueue.length === 0) {
|
||||
isMoving = false;
|
||||
if (queueCheckInterval) clearInterval(queueCheckInterval);
|
||||
|
||||
// Restore speed if it was altered
|
||||
await MotionEffects.onSequenceEnd(currentMotionMode);
|
||||
|
||||
if (currentResolve) currentResolve(true);
|
||||
currentResolve = null;
|
||||
return;
|
||||
@@ -60,102 +68,135 @@ async function executeQueue() {
|
||||
isMoving = true;
|
||||
let target = motionQueue.shift();
|
||||
|
||||
const cmd = `X:${target.s1},${target.s2}`;
|
||||
if (BLE.isConnected()) {
|
||||
try {
|
||||
await BLE.write(cmd);
|
||||
} catch (e) {
|
||||
UI.log(`BLE error: ${e.message}`, 'error');
|
||||
motionQueue = []; // clear queue on error
|
||||
isMoving = false;
|
||||
if (queueCheckInterval) clearInterval(queueCheckInterval);
|
||||
if (currentResolve) currentResolve(false);
|
||||
currentResolve = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup interval to monitor progress
|
||||
if (queueCheckInterval) clearInterval(queueCheckInterval);
|
||||
queueCheckInterval = setInterval(async () => {
|
||||
// Calculate distance to target in steps
|
||||
const dist1 = Math.abs(target.s1 - steps1);
|
||||
const dist2 = Math.abs(target.s2 - steps2);
|
||||
const maxDist = Math.max(dist1, dist2);
|
||||
const { delay: delayBeforeSend, hasHesitated: newHasHesitated } = await MotionEffects.onWaypointSend(target, hasHesitated);
|
||||
hasHesitated = newHasHesitated;
|
||||
|
||||
if (motionQueue.length > 0) {
|
||||
// We have more waypoints. Blend!
|
||||
if (target.mode === 'direct') {
|
||||
if (maxDist <= CONFIG.BLEND_THRESHOLD_STEPS) {
|
||||
clearInterval(queueCheckInterval);
|
||||
executeQueue(); // start next waypoint early
|
||||
}
|
||||
} else if (target.mode === 'erratic') {
|
||||
if (maxDist <= 5) { // wait for full stop
|
||||
clearInterval(queueCheckInterval);
|
||||
setTimeout(executeQueue, 150); // slight pause for erratic effect
|
||||
const doSend = async () => {
|
||||
const cmd = `X:${target.s1},${target.s2}`;
|
||||
if (BLE.isConnected()) {
|
||||
try {
|
||||
await BLE.write(cmd);
|
||||
} catch (e) {
|
||||
UI.log(`BLE error: ${e.message}`, 'error');
|
||||
motionQueue = []; // clear queue on error
|
||||
isMoving = false;
|
||||
if (queueCheckInterval) clearInterval(queueCheckInterval);
|
||||
if (currentResolve) currentResolve(false);
|
||||
currentResolve = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup interval to monitor progress
|
||||
if (queueCheckInterval) clearInterval(queueCheckInterval);
|
||||
queueCheckInterval = setInterval(async () => {
|
||||
// Calculate distance to target in steps
|
||||
const dist1 = Math.abs(target.s1 - steps1);
|
||||
const dist2 = Math.abs(target.s2 - steps2);
|
||||
const maxDist = Math.max(dist1, dist2);
|
||||
|
||||
if (motionQueue.length > 0) {
|
||||
const nextTarget = motionQueue[0];
|
||||
const queueAction = MotionEffects.getQueueAction(target.mode, nextTarget.mode, maxDist, CONFIG.BLEND_THRESHOLD_STEPS);
|
||||
if (queueAction.action === 'wait') {
|
||||
clearInterval(queueCheckInterval);
|
||||
setTimeout(executeQueue, queueAction.delay);
|
||||
} else if (queueAction.action === 'blend') {
|
||||
clearInterval(queueCheckInterval);
|
||||
executeQueue();
|
||||
}
|
||||
} else {
|
||||
// Default to direct blending
|
||||
if (maxDist <= CONFIG.BLEND_THRESHOLD_STEPS) {
|
||||
clearInterval(queueCheckInterval);
|
||||
executeQueue();
|
||||
// Last waypoint. Wait until closely reached.
|
||||
if (maxDist <= 10) {
|
||||
clearInterval(queueCheckInterval);
|
||||
motionQueue = [];
|
||||
executeQueue(); // Will trigger resolve
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Last waypoint. Wait until closely reached.
|
||||
if (maxDist <= 10) {
|
||||
clearInterval(queueCheckInterval);
|
||||
motionQueue = [];
|
||||
executeQueue(); // Will trigger resolve
|
||||
}
|
||||
}, 40); // check ~25 times a second
|
||||
|
||||
} else {
|
||||
// Simulation: instantly jump to final target
|
||||
const finalTarget = motionQueue.length > 0 ? motionQueue[motionQueue.length - 1] : target;
|
||||
steps1 = finalTarget.s1;
|
||||
steps2 = finalTarget.s2;
|
||||
console.log(`[sim] Jumped to X:${steps1},${steps2} via trajectory`);
|
||||
|
||||
// Trigger pseudo-status event for the UI to update markers
|
||||
document.dispatchEvent(new CustomEvent('ble:status', { detail: `P:${steps1},${steps2}` }));
|
||||
|
||||
motionQueue = [];
|
||||
isMoving = false;
|
||||
if (currentResolve) {
|
||||
currentResolve(true);
|
||||
currentResolve = null;
|
||||
}
|
||||
}, 40); // check ~25 times a second
|
||||
|
||||
} else {
|
||||
// Simulation: instantly jump to final target
|
||||
const finalTarget = motionQueue.length > 0 ? motionQueue[motionQueue.length - 1] : target;
|
||||
steps1 = finalTarget.s1;
|
||||
steps2 = finalTarget.s2;
|
||||
console.log(`[sim] Jumped to X:${steps1},${steps2} via trajectory`);
|
||||
|
||||
// Trigger pseudo-status event for the UI to update markers
|
||||
document.dispatchEvent(new CustomEvent('ble:status', { detail: `P:${steps1},${steps2}` }));
|
||||
|
||||
motionQueue = [];
|
||||
isMoving = false;
|
||||
if (currentResolve) {
|
||||
currentResolve(true);
|
||||
currentResolve = null;
|
||||
}
|
||||
};
|
||||
|
||||
if (delayBeforeSend > 0) {
|
||||
setTimeout(doSend, delayBeforeSend);
|
||||
} else {
|
||||
doSend();
|
||||
}
|
||||
}
|
||||
|
||||
function buildSegments(startX, startY, endX, endY, mode) {
|
||||
const dx = endX - startX;
|
||||
const dy = endY - startY;
|
||||
const modDest = MotionEffects.modifyDestination(startX, startY, endX, endY, mode);
|
||||
const actualEndX = modDest.endX;
|
||||
const actualEndY = modDest.endY;
|
||||
|
||||
const dx = actualEndX - startX;
|
||||
const dy = actualEndY - startY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
let numSegments = Math.ceil(distance / CONFIG.SEGMENT_SIZE_MM);
|
||||
if (numSegments === 0) numSegments = 1;
|
||||
|
||||
let numSegments = MotionEffects.getSegmentCount(distance, CONFIG.SEGMENT_SIZE_MM, mode);
|
||||
|
||||
let segs = [];
|
||||
|
||||
const nx = -dy / distance || 0;
|
||||
const ny = dx / distance || 0;
|
||||
|
||||
for (let i = 1; i <= numSegments; i++) {
|
||||
const fraction = i / numSegments;
|
||||
const px = startX + dx * fraction;
|
||||
const py = startY + dy * fraction;
|
||||
let px = startX + dx * fraction;
|
||||
let py = startY + dy * fraction;
|
||||
|
||||
const perturbed = MotionEffects.applyPerturbation(px, py, nx, ny, fraction, distance, mode);
|
||||
px = perturbed.px;
|
||||
py = perturbed.py;
|
||||
|
||||
const wsCheck = IK.checkWorkspace(px, py);
|
||||
if (!wsCheck.ok) return null;
|
||||
if (!wsCheck.ok) {
|
||||
px = startX + dx * fraction;
|
||||
py = startY + dy * fraction;
|
||||
}
|
||||
|
||||
const res = IK.solve(px, py);
|
||||
if (!res.reachable || IK.armsCrossed(res.theta1, res.theta2)) return null;
|
||||
if (!res.reachable || IK.armsCrossed(res.theta1, res.theta2)) {
|
||||
px = startX + dx * fraction;
|
||||
py = startY + dy * fraction;
|
||||
const fallbackRes = IK.solve(px, py);
|
||||
if (!fallbackRes.reachable || IK.armsCrossed(fallbackRes.theta1, fallbackRes.theta2)) {
|
||||
return null;
|
||||
}
|
||||
segs.push({
|
||||
s1: IK.radToStepsAbsolute(fallbackRes.theta1, 1),
|
||||
s2: IK.radToStepsAbsolute(fallbackRes.theta2, 2),
|
||||
mode,
|
||||
fraction
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const s1 = IK.radToStepsAbsolute(res.theta1, 1);
|
||||
const s2 = IK.radToStepsAbsolute(res.theta2, 2);
|
||||
|
||||
segs.push({ s1, s2, mode });
|
||||
segs.push({ s1, s2, mode, fraction });
|
||||
}
|
||||
|
||||
const settlingSegs = MotionEffects.getSettlingWaypoints(endX, endY, mode);
|
||||
segs = segs.concat(settlingSegs);
|
||||
|
||||
return segs;
|
||||
}
|
||||
|
||||
@@ -170,6 +211,11 @@ async function goto(targetX, targetY, mode = 'direct') {
|
||||
return false;
|
||||
}
|
||||
|
||||
mode = MotionEffects.resolveMode(mode);
|
||||
|
||||
currentMotionMode = mode;
|
||||
hasHesitated = false;
|
||||
|
||||
// Quick check on final destination
|
||||
const destCheck = IK.checkWorkspace(targetX, targetY);
|
||||
if (!destCheck.ok) {
|
||||
@@ -218,6 +264,8 @@ async function goto(targetX, targetY, mode = 'direct') {
|
||||
|
||||
motionQueue = segments;
|
||||
|
||||
await MotionEffects.onSequenceStart(mode);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
currentResolve = resolve;
|
||||
executeQueue();
|
||||
|
||||
Reference in New Issue
Block a user