import IK from './kinematics.js'; import Motion from './motion.js'; let activeMode = 'none'; // 'none', 'circles', 'erratic', 'park-bounce' let parkPosition = { x: 0, y: 80 }; let anchorPos = { x: 0, y: 80 }; let timerId = null; let currentAngle = 0; let bounceState = 'to-park'; // 'to-park' or 'to-anchor' const IDLE_WAIT_MS = 5000; const CONTINUOUS_WAIT_MS = 500; // 0.5s pause between idle movements const RADIUS = 20; export const IdleEffects = { setMode(mode) { activeMode = mode || 'none'; localStorage.setItem('wiji_idle_mode', activeMode); // Update all dropdowns in UI to keep them in sync document.querySelectorAll('.idle-mode-select').forEach(el => { if (el.value !== activeMode) el.value = activeMode; }); if (activeMode === 'none') { this.stopTimer(); } else { if (!Motion.isMoving && localStorage.getItem('wiji_homed') === 'true') { this.updateAnchorPos(); this.startTimer(IDLE_WAIT_MS); } } }, getMode() { return activeMode; }, setParkPosition(x, y) { parkPosition = { x, y }; }, updateAnchorPos() { const { steps1, steps2 } = Motion.getSteps(); const pos = IK.forward(IK.stepsToRad(steps1), IK.stepsToRad(steps2)); if (pos.valid) { anchorPos = { x: pos.endX, y: pos.endY }; currentAngle = 0; bounceState = 'to-park'; } }, startTimer(ms) { this.stopTimer(); if (activeMode === 'none') return; timerId = setTimeout(() => { this.triggerNextMove(); }, ms); }, stopTimer() { if (timerId) { clearTimeout(timerId); timerId = null; } }, async triggerNextMove() { if (activeMode === 'none' || localStorage.getItem('wiji_homed') !== 'true') return; let targetX = anchorPos.x; let targetY = anchorPos.y; let motionMode = 'direct'; if (activeMode === 'circles') { currentAngle += Math.PI / 4; // 45 degrees targetX = anchorPos.x + RADIUS * Math.cos(currentAngle); targetY = anchorPos.y + RADIUS * Math.sin(currentAngle); motionMode = 'direct'; } else if (activeMode === 'erratic') { const randAngle = Math.random() * Math.PI * 2; const randR = Math.random() * RADIUS; targetX = anchorPos.x + randR * Math.cos(randAngle); targetY = anchorPos.y + randR * Math.sin(randAngle); motionMode = 'erratic'; } else if (activeMode === 'park-bounce') { if (bounceState === 'to-park') { targetX = parkPosition.x; targetY = parkPosition.y; bounceState = 'to-anchor'; } else { targetX = anchorPos.x; targetY = anchorPos.y; bounceState = 'to-park'; } motionMode = 'direct'; } // Workspace check const check = IK.checkWorkspace(targetX, targetY); if (!check.ok) { // If out of bounds, skip this move and wait for next this.startTimer(CONTINUOUS_WAIT_MS); return; } // Fire the idle move! try { await Motion.goto(targetX, targetY, motionMode, true); } catch(e) { // If aborted, it's fine. } }, init() { const saved = localStorage.getItem('wiji_idle_mode'); if (saved) activeMode = saved; document.addEventListener('wiji:motion-start', (e) => { const isIdleMove = e.detail && e.detail.isIdleMove; if (!isIdleMove) { this.stopTimer(); } }); document.addEventListener('wiji:motion-end', (e) => { if (activeMode === 'none' || localStorage.getItem('wiji_homed') !== 'true') return; const isIdleMove = e.detail && e.detail.isIdleMove; if (!isIdleMove) { this.updateAnchorPos(); this.startTimer(IDLE_WAIT_MS); } else { this.startTimer(CONTINUOUS_WAIT_MS); } }); document.addEventListener('wiji:unhomed', () => { this.setMode('none'); }); } }; // Auto-init globally IdleEffects.init();