File size: 5,271 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import type { RobotConnection, LiveCalibrationData, WebCalibrationResults, TeleoperationState } from "@/types/robot"

const MOCK_MOTOR_NAMES = ["shoulder_pan", "shoulder_lift", "elbow_flex", "wrist_flex", "wrist_roll", "gripper"]

export const lerobot = {
  isWebSerialSupported: () => true,
  findPort: async ({
    robotConfigs,
    onMessage,
  }: { robotConfigs?: any[]; onMessage?: (msg: string) => void }): Promise<{ result: Promise<RobotConnection[]> }> => {
    onMessage?.("Searching for robots...")
    const resultPromise = new Promise<RobotConnection[]>((resolve) => {
      setTimeout(() => {
        if (robotConfigs && robotConfigs.length > 0) {
          onMessage?.(`Found ${robotConfigs.length} saved robots.`)
          const reconnectedRobots = robotConfigs.map((config) => ({
            ...config,
            isConnected: Math.random() > 0.3,
            port: {},
          }))
          resolve(reconnectedRobots)
        } else {
          onMessage?.("Simulating successful device connection.")
          const newRobot: RobotConnection = {
            port: {},
            name: "Cyber-Arm 7",
            isConnected: true,
            robotType: "so100_follower",
            robotId: `robot-${Date.now()}`,
            serialNumber: `SN-${Math.floor(Math.random() * 1000000)}`,
          }
          resolve([newRobot])
        }
      }, 1500)
    })
    return { result: resultPromise }
  },
  calibrate: async (
    robot: RobotConnection,
    {
      onLiveUpdate,
      onProgress,
    }: { onLiveUpdate: (data: LiveCalibrationData) => void; onProgress: (msg: string) => void },
  ): Promise<{ result: Promise<WebCalibrationResults>; stop: () => void }> => {
    onProgress("MOCK: Starting calibration... Move all joints to their limits.")
    let intervalId: NodeJS.Timeout
    const liveData: LiveCalibrationData = MOCK_MOTOR_NAMES.reduce((acc, name) => {
      acc[name] = { current: 2048, min: 4095, max: 0, range: 0 }
      return acc
    }, {} as LiveCalibrationData)
    intervalId = setInterval(() => {
      MOCK_MOTOR_NAMES.forEach((name) => {
        const motor = liveData[name]
        motor.current = 2048 + Math.floor((Math.random() - 0.5) * 4000)
        motor.min = Math.min(motor.min, motor.current)
        motor.max = Math.max(motor.max, motor.current)
        motor.range = motor.max - motor.min
      })
      onLiveUpdate({ ...liveData })
    }, 100)
    const resultPromise = new Promise<WebCalibrationResults>((resolve) => {
      ;(stop as any)._resolver = resolve
    })
    const stop = () => {
      clearInterval(intervalId)
      onProgress("MOCK: Calibration recording finished.")
      const finalResults = MOCK_MOTOR_NAMES.reduce((acc, name) => {
        acc[name] = { min: liveData[name].min, max: liveData[name].max }
        return acc
      }, {} as WebCalibrationResults)
      ;(stop as any)._resolver(finalResults)
    }
    return { result: resultPromise, stop }
  },
  teleoperate: async (
    robot: RobotConnection,
    {
      calibrationData,
      onStateUpdate,
    }: { calibrationData: WebCalibrationResults; onStateUpdate: (state: TeleoperationState) => void },
  ): Promise<{
    start: () => void
    stop: () => void
    updateKeyState: (key: string, pressed: boolean) => void
    moveMotor: (motorName: string, position: number) => void
  }> => {
    let isActive = false
    let intervalId: NodeJS.Timeout
    const keyStates: { [key: string]: { pressed: boolean } } = {}
    const motorConfigs = MOCK_MOTOR_NAMES.map((name) => ({
      name,
      currentPosition: calibrationData[name] ? (calibrationData[name].min + calibrationData[name].max) / 2 : 2048,
      minPosition: calibrationData[name]?.min ?? 0,
      maxPosition: calibrationData[name]?.max ?? 4095,
    }))
    const teleopState: TeleoperationState = { isActive, motorConfigs, keyStates }
    const updateLoop = () => onStateUpdate({ ...teleopState, motorConfigs: [...motorConfigs] })
    return {
      start: () => {
        if (isActive) return
        isActive = true
        teleopState.isActive = true
        intervalId = setInterval(updateLoop, 100)
        onStateUpdate({ ...teleopState })
      },
      stop: () => {
        if (!isActive) return
        isActive = false
        teleopState.isActive = false
        clearInterval(intervalId)
        onStateUpdate({ ...teleopState })
      },
      updateKeyState: (key: string, pressed: boolean) => {
        keyStates[key] = { pressed }
        teleopState.keyStates = { ...keyStates }
        onStateUpdate({ ...teleopState })
      },
      moveMotor: (motorName: string, position: number) => {
        const motor = motorConfigs.find((m) => m.name === motorName)
        if (motor) {
          motor.currentPosition = position
          onStateUpdate({ ...teleopState, motorConfigs: [...motorConfigs] })
        }
      },
    }
  },
  SO100_KEYBOARD_CONTROLS: {
    shoulder_pan: { positive: "ArrowRight", negative: "ArrowLeft" },
    shoulder_lift: { positive: "ArrowUp", negative: "ArrowDown" },
    elbow_flex: { positive: "w", negative: "s" },
    wrist_flex: { positive: "a", negative: "d" },
    wrist_roll: { positive: "q", negative: "e" },
    gripper: { positive: "o", negative: "c" },
    stop: "Escape",
  },
  MOCK_MOTOR_NAMES,
}