import { useState, useCallback, useMemo } from "react"; import { Button } from "./ui/button.js"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "./ui/card.js"; import { Badge } from "./ui/badge.js"; import { calibrate, releaseMotors, type WebCalibrationResults, type LiveCalibrationData, type CalibrationProcess, } from "@lerobot/web"; import { CalibrationModal } from "./CalibrationModal.js"; import type { RobotConnection } from "@lerobot/web"; interface CalibrationPanelProps { robot: RobotConnection; onFinish: () => void; } export function CalibrationPanel({ robot, onFinish }: CalibrationPanelProps) { // Simple state management const [isCalibrating, setIsCalibrating] = useState(false); const [calibrationResult, setCalibrationResult] = useState(null); const [status, setStatus] = useState("Ready to calibrate"); const [modalOpen, setModalOpen] = useState(false); const [calibrationProcess, setCalibrationProcess] = useState(null); const [motorData, setMotorData] = useState({}); const [isPreparing, setIsPreparing] = useState(false); // Motor names for display const motorNames = useMemo( () => [ "shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll", "gripper", ], [] ); // Initialize motor data const initializeMotorData = useCallback(() => { const initialData: LiveCalibrationData = {}; motorNames.forEach((name) => { initialData[name] = { current: 2047, min: 2047, max: 2047, range: 0, }; }); setMotorData(initialData); }, [motorNames]); // Release motor torque const releaseMotorTorque = useCallback(async () => { try { setIsPreparing(true); setStatus("🔓 Releasing motor torque - joints can now be moved freely"); await releaseMotors(robot); setStatus("✅ Joints are now free to move - set your homing position"); } catch (error) { console.warn("Failed to release motor torque:", error); setStatus("⚠️ Could not release motor torque - try moving joints gently"); } finally { setIsPreparing(false); } }, [robot]); // Start calibration using new API const handleContinueCalibration = useCallback(async () => { setModalOpen(false); if (!robot.port || !robot.robotType) { return; } try { setStatus("🤖 Starting calibration process..."); setIsCalibrating(true); initializeMotorData(); // Use the unified config API for calibration const process = await calibrate({ robot, onLiveUpdate: (data) => { setMotorData(data); setStatus( "📏 Recording joint ranges - move all joints through their full range" ); }, onProgress: (message) => { setStatus(message); }, }); setCalibrationProcess(process); // Add Enter key listener for stopping (matching Node.js UX) const handleKeyPress = (event: KeyboardEvent) => { if (event.key === "Enter") { process.stop(); } }; document.addEventListener("keydown", handleKeyPress); try { // Wait for calibration to complete const result = await process.result; setCalibrationResult(result); // App-level concern: Save results to storage const serialNumber = robot.serialNumber || robot.usbMetadata?.serialNumber || "unknown"; await saveCalibrationResults( result, robot.robotType, robot.robotId || `${robot.robotType}_1`, serialNumber ); setStatus( "✅ Calibration completed successfully! Configuration saved." ); } finally { document.removeEventListener("keydown", handleKeyPress); setCalibrationProcess(null); setIsCalibrating(false); } } catch (error) { console.error("❌ Calibration failed:", error); setStatus( `❌ Calibration failed: ${ error instanceof Error ? error.message : error }` ); setIsCalibrating(false); setCalibrationProcess(null); } }, [robot, initializeMotorData]); // Stop calibration recording const handleStopRecording = useCallback(() => { if (calibrationProcess) { calibrationProcess.stop(); } }, [calibrationProcess]); // App-level concern: Save calibration results const saveCalibrationResults = async ( results: WebCalibrationResults, robotType: string, robotId: string, serialNumber: string ) => { try { // Save to unified storage (app-level functionality) const { saveCalibrationData } = await import("../lib/unified-storage.js"); const fullCalibrationData = { ...results, device_type: robotType, device_id: robotId, calibrated_at: new Date().toISOString(), platform: "web", api: "Web Serial API", }; const metadata = { timestamp: new Date().toISOString(), readCount: Object.keys(motorData).length > 0 ? 100 : 0, // Estimate }; saveCalibrationData(serialNumber, fullCalibrationData, metadata); } catch (error) { console.warn("Failed to save calibration results:", error); } }; // App-level concern: JSON export functionality const downloadConfigJSON = useCallback(() => { if (!calibrationResult) return; const jsonString = JSON.stringify(calibrationResult, null, 2); const blob = new Blob([jsonString], { type: "application/json" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = `${robot.robotId || robot.robotType}_calibration.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }, [calibrationResult, robot.robotId, robot.robotType]); return (
{/* Calibration Status Card */}
🛠️ Calibrating: {robot.robotId} {robot.robotType?.replace("_", " ")} • {robot.name}
{isCalibrating ? "Recording" : calibrationResult ? "Complete" : "Ready"}

Status:

{status}

{isCalibrating && (

Move joints through full range | Press "Finish Recording" or Enter key when done

)}
{!isCalibrating && !calibrationResult && ( )} {isCalibrating && calibrationProcess && ( )} {calibrationResult && ( <> )}
{/* Configuration JSON Display */} {calibrationResult && ( 🎯 Calibration Configuration Copy this JSON or download it for your robot setup
                {JSON.stringify(calibrationResult, null, 2)}
              
)} {/* Live Position Recording Table */} Live Position Recording Real-time motor position feedback during calibration
{motorNames.map((motorName) => { const motor = motorData[motorName] || { current: 2047, min: 2047, max: 2047, range: 0, }; return ( ); })}
Motor Name Current Min Max Range
{motorName} {motor.range > 100 && ( )} {motor.current} {motor.min} {motor.max} 100 ? "text-green-600" : "text-gray-500" } > {motor.range}
{isCalibrating && (
Move joints through their full range of motion...
)}
{/* Calibration Modal */}
); }