File size: 4,054 Bytes
a8d792f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"use client"
import { useState, useMemo } from "react"
import { Download } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { lerobot } from "@/lib/mock-api"
import { useLocalStorage } from "@/hooks/use-local-storage"
import { MotorCalibrationVisual } from "@/components/motor-calibration-visual"
import type { RobotConnection, LiveCalibrationData, WebCalibrationResults } from "@/types/robot"

interface CalibrationViewProps {
  robot: RobotConnection
}

export function CalibrationView({ robot }: CalibrationViewProps) {
  const [status, setStatus] = useState("Ready to calibrate.")
  const [liveData, setLiveData] = useState<LiveCalibrationData | null>(null)
  const [isCalibrating, setIsCalibrating] = useState(false)
  const [calibrationProcess, setCalibrationProcess] = useState<{
    stop: () => void
    result: Promise<WebCalibrationResults>
  } | null>(null)
  const [calibrationResults, setCalibrationResults] = useLocalStorage<WebCalibrationResults | null>(
    `calibration-${robot.robotId}`,
    null,
  )

  const handleStart = async () => {
    setIsCalibrating(true)
    const process = await lerobot.calibrate(robot, {
      onLiveUpdate: setLiveData,
      onProgress: setStatus,
    })
    setCalibrationProcess(process)
  }

  const handleFinish = async () => {
    if (calibrationProcess) {
      calibrationProcess.stop()
      const results = await calibrationProcess.result
      setCalibrationResults(results)
      setIsCalibrating(false)
      setCalibrationProcess(null)
    }
  }

  const downloadJson = () => {
    if (!calibrationResults) return
    const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(calibrationResults, null, 2))
    const downloadAnchorNode = document.createElement("a")
    downloadAnchorNode.setAttribute("href", dataStr)
    downloadAnchorNode.setAttribute("download", `${robot.robotId}_calibration.json`)
    document.body.appendChild(downloadAnchorNode)
    downloadAnchorNode.click()
    downloadAnchorNode.remove()
  }

  const motorData = useMemo(
    () =>
      liveData
        ? Object.entries(liveData)
        : lerobot.MOCK_MOTOR_NAMES.map((name) => [name, { current: 0, min: 4095, max: 0, range: 0 }]),
    [liveData],
  )

  return (
    <Card className="border-0 rounded-none">
      <div className="p-4 border-b border-white/10 flex items-center justify-between">
        <div className="flex items-center gap-4">
          <div className="w-1 h-8 bg-primary"></div>
          <div>
            <h3 className="text-xl font-bold text-foreground font-mono tracking-wider uppercase">motor calibration</h3>
            <p className="text-sm text-muted-foreground font-mono">move all joints to their limits</p>
          </div>
        </div>
        <div className="flex gap-4">
          {!isCalibrating ? (
            <Button onClick={handleStart} size="lg">
              Start Calibration
            </Button>
          ) : (
            <Button onClick={handleFinish} variant="destructive" size="lg">
              Finish Recording
            </Button>
          )}
          <Button onClick={downloadJson} variant="outline" size="lg" disabled={!calibrationResults}>
            <Download className="w-4 h-4 mr-2" /> Download JSON
          </Button>
        </div>
      </div>
      <div className="pt-6 p-6">
        <div className="flex items-center gap-4 py-2 px-4 text-sm font-sans text-muted-foreground">
          <div className="w-40">Motor Name</div>
          <div className="flex-1">Visual Range</div>
          <div className="w-16 text-right">Current</div>
          <div className="w-16 text-right">Min</div>
          <div className="w-16 text-right">Max</div>
          <div className="w-16 text-right">Range</div>
        </div>
        <div className="border-t border-white/10">
          {motorData.map(([name, data]) => (
            <MotorCalibrationVisual key={name} name={name} data={data} />
          ))}
        </div>
      </div>
    </Card>
  )
}