Spaces:
Running
Running
/** | |
* Sequential Operations Test Logic | |
* Tests: findPort โ calibrate โ releaseMotors โ teleoperate | |
*/ | |
import { findPort, calibrate, releaseMotors, teleoperate } from "@lerobot/web"; | |
let isRunning = false; | |
function log(message: string) { | |
const logElement = document.getElementById("log"); | |
if (logElement) { | |
const timestamp = new Date().toLocaleTimeString(); | |
logElement.textContent += `[${timestamp}] ${message}\n`; | |
logElement.scrollTop = logElement.scrollHeight; | |
} | |
} | |
function setButtonState(running: boolean) { | |
isRunning = running; | |
const button = document.getElementById("runTest") as HTMLButtonElement; | |
if (button) { | |
button.disabled = running; | |
button.textContent = running | |
? "โณ Running Test..." | |
: "๐ Run Sequential Operations Test"; | |
} | |
} | |
declare global { | |
interface Window { | |
clearLog: () => void; | |
runSequentialTest: () => Promise<void>; | |
} | |
} | |
window.clearLog = function () { | |
const logElement = document.getElementById("log"); | |
if (logElement) { | |
logElement.textContent = "Log cleared.\n"; | |
} | |
}; | |
window.runSequentialTest = async function () { | |
if (isRunning) return; | |
setButtonState(true); | |
log("๐ Starting sequential operations test..."); | |
try { | |
// Step 1: Find port | |
log("\n1๏ธโฃ Finding robot port..."); | |
const findProcess = await findPort(); | |
const robots = await findProcess.result; | |
const robot = robots[0]; // Get first robot | |
if (!robot || !robot.isConnected) { | |
throw new Error("No robot found or robot not connected"); | |
} | |
log(`โ Found robot: ${robot.name} (${robot.robotType})`); | |
log(` Serial: ${robot.serialNumber}`); | |
// Step 2: Release motors first for calibration setup | |
log("\n2๏ธโฃ Releasing motors for calibration setup..."); | |
if (!robot.robotType) { | |
throw new Error("Robot type not configured"); | |
} | |
log("๐ Releasing all motors..."); | |
await releaseMotors(robot); | |
log("โ Motors released - you can now move the arm freely!"); | |
// Now start calibration | |
log("\n๐ Starting calibration with live position updates..."); | |
log("๐ก Move the arm through its range of motion to see live updates!"); | |
const useSimulatedCalibration = false; // Real calibration to see live updates | |
let calibrationResult: any; | |
if (useSimulatedCalibration) { | |
// Simulated calibration data (for testing without robot movement) | |
log("๐ Using simulated calibration data for testing..."); | |
await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulate calibration time | |
calibrationResult = { | |
shoulder_pan: { | |
id: 1, | |
drive_mode: 0, | |
homing_offset: 34, | |
range_min: 994, | |
range_max: 3100, | |
}, | |
shoulder_lift: { | |
id: 2, | |
drive_mode: 0, | |
homing_offset: 991, | |
range_min: 960, | |
range_max: 3233, | |
}, | |
elbow_flex: { | |
id: 3, | |
drive_mode: 0, | |
homing_offset: -881, | |
range_min: 1029, | |
range_max: 3065, | |
}, | |
wrist_flex: { | |
id: 4, | |
drive_mode: 0, | |
homing_offset: 128, | |
range_min: 710, | |
range_max: 3135, | |
}, | |
wrist_roll: { | |
id: 5, | |
drive_mode: 0, | |
homing_offset: -15, | |
range_min: 0, | |
range_max: 4095, | |
}, | |
gripper: { | |
id: 6, | |
drive_mode: 0, | |
homing_offset: -1151, | |
range_min: 2008, | |
range_max: 3606, | |
}, | |
}; | |
log("โ Calibration completed (simulated)"); | |
} else { | |
// Real calibration | |
const calibrationProcess = await calibrate(robot, { | |
onProgress: (message) => log(`๐ ${message}`), | |
onLiveUpdate: (data) => { | |
const motors = Object.keys(data); | |
if (motors.length > 0) { | |
const ranges = motors.map((m) => data[m].range).join(", "); | |
log(`๐ Motor ranges: [${ranges}]`); | |
} | |
}, | |
}); | |
// Auto-stop calibration after 8 seconds for testing | |
setTimeout(() => { | |
log("โฑ๏ธ Auto-stopping calibration for test..."); | |
calibrationProcess.stop(); | |
}, 8000); | |
await calibrationProcess.result; | |
log("โ Calibration completed (real)"); | |
} | |
// Step 3: Use your provided calibration config for teleoperation | |
log("\n3๏ธโฃ Setting up teleoperation with your calibration config..."); | |
// Use your provided calibration config instead of real calibration result | |
calibrationResult = { | |
shoulder_pan: { | |
id: 1, | |
drive_mode: 0, | |
homing_offset: 34, | |
range_min: 994, | |
range_max: 3100, | |
}, | |
shoulder_lift: { | |
id: 2, | |
drive_mode: 0, | |
homing_offset: 991, | |
range_min: 960, | |
range_max: 3233, | |
}, | |
elbow_flex: { | |
id: 3, | |
drive_mode: 0, | |
homing_offset: -881, | |
range_min: 1029, | |
range_max: 3065, | |
}, | |
wrist_flex: { | |
id: 4, | |
drive_mode: 0, | |
homing_offset: 128, | |
range_min: 710, | |
range_max: 3135, | |
}, | |
wrist_roll: { | |
id: 5, | |
drive_mode: 0, | |
homing_offset: -15, | |
range_min: 0, | |
range_max: 4095, | |
}, | |
gripper: { | |
id: 6, | |
drive_mode: 0, | |
homing_offset: -1151, | |
range_min: 2008, | |
range_max: 3606, | |
}, | |
}; | |
log( | |
`โ Using your calibration config: ${Object.keys(calibrationResult).join( | |
", " | |
)}` | |
); | |
// Step 4: Teleoperate with auto key simulation | |
log("\n4๏ธโฃ Starting teleoperation..."); | |
const teleoperationProcess = await teleoperate(robot, { | |
calibrationData: calibrationResult, | |
onStateUpdate: (state) => { | |
if (state.isActive && Object.keys(state.keyStates).length > 0) { | |
const activeKeys = Object.keys(state.keyStates).filter( | |
(k) => state.keyStates[k].pressed | |
); | |
log(`๐ฎ Auto-simulated keys: ${activeKeys.join(", ")}`); | |
} | |
}, | |
}); | |
teleoperationProcess.start(); | |
log("โ Teleoperation started"); | |
log("๐ค Auto-simulating arrow key presses to move the arm..."); | |
// Auto-simulate key presses (left/right arrows to move shoulder pan) | |
let keySimulationActive = true; | |
let currentDirection = "ArrowLeft"; | |
const simulateKeys = () => { | |
if (!keySimulationActive) return; | |
// Press current key | |
teleoperationProcess.updateKeyState(currentDirection, true); | |
log(`๐ Pressing ${currentDirection} (shoulder pan movement)`); | |
// Hold for 1 second, then release and switch direction | |
setTimeout(() => { | |
if (!keySimulationActive) return; | |
teleoperationProcess.updateKeyState(currentDirection, false); | |
// Switch direction | |
currentDirection = | |
currentDirection === "ArrowLeft" ? "ArrowRight" : "ArrowLeft"; | |
// Wait 500ms then start next movement | |
setTimeout(() => { | |
if (keySimulationActive) simulateKeys(); | |
}, 500); | |
}, 1000); | |
}; | |
// Start key simulation after a brief delay | |
setTimeout(simulateKeys, 1000); | |
// Auto-stop teleoperation after 8 seconds for testing | |
setTimeout(() => { | |
keySimulationActive = false; | |
log("โฑ๏ธ Auto-stopping teleoperation for test..."); | |
teleoperationProcess.stop(); | |
log("\n๐ All sequential operations completed successfully!"); | |
setButtonState(false); | |
}, 8000); | |
} catch (error: any) { | |
log(`โ Sequential operations failed: ${error.message}`); | |
// Check if it's a connection conflict | |
if ( | |
error.message.includes("port") || | |
error.message.includes("serial") || | |
error.message.includes("connection") | |
) { | |
log("๐ This might be a port connection conflict!"); | |
log("๐ก Multiple WebSerialPortWrapper instances may be interfering"); | |
} | |
setButtonState(false); | |
} | |
}; | |
// Initialize on DOM load | |
document.addEventListener("DOMContentLoaded", () => { | |
// Check Web Serial support | |
if (!("serial" in navigator)) { | |
log("โ Web Serial API not supported in this browser"); | |
log("๐ก Try Chrome/Edge with --enable-web-serial flag"); | |
const button = document.getElementById("runTest") as HTMLButtonElement; | |
if (button) button.disabled = true; | |
} else { | |
log("โ Web Serial API supported"); | |
log("Ready to test. Click 'Run Sequential Operations Test' to begin..."); | |
} | |
}); | |