File size: 7,704 Bytes
6ce4ca6
 
3cdf7b9
 
 
 
f62f94b
3cdf7b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f62f94b
 
 
 
3cdf7b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f62f94b
 
3cdf7b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f62f94b
3cdf7b9
 
 
 
 
6ce4ca6
 
 
3cdf7b9
 
 
 
 
 
 
 
 
 
f62f94b
3cdf7b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f62f94b
 
3cdf7b9
 
 
 
 
f62f94b
3cdf7b9
 
 
 
f62f94b
 
3cdf7b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f62f94b
 
3cdf7b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
<!-- USB Calibration Panel - Compact Modal Version -->
<script lang="ts">
	import { Button } from "@/components/ui/button/index.js";
	import { Badge } from "@/components/ui/badge/index.js";

	interface Props {
		calibrationManager: any; // USBServoDriver (Consumer or Producer)
		connectionType?: "consumer" | "producer";
		onCalibrationComplete?: () => void;
		onCancel?: () => void;
	}

	let {
		calibrationManager,
		connectionType = "consumer",
		onCalibrationComplete,
		onCancel
	}: Props = $props();

	// Joint names for reference
	const jointNames = ["Rotation", "Pitch", "Elbow", "Wrist_Pitch", "Wrist_Roll", "Jaw"];

	// Reactive getters from the calibration state
	const isCalibrating = $derived(calibrationManager.calibrationState.isCalibrating);
	const progress = $derived(calibrationManager.calibrationState.progress);
	const isCalibrated = $derived(calibrationManager.calibrationState.isCalibrated);
	const needsCalibration = $derived(calibrationManager.calibrationState.needsCalibration);

	// Connection type descriptions
	const connectionInfo = $derived(
		connectionType === "producer"
			? {
					lockStatus: "πŸ”’ Servos will be LOCKED for control",
					lockDescription: "Robot controlled by software"
				}
			: {
					lockStatus: "πŸ”“ Servos remain UNLOCKED for manual movement",
					lockDescription: "Robot can be moved manually"
				}
	);


	async function startCalibration() {
		await calibrationManager.startCalibration();
	}

	async function completeCalibration() {
		await calibrationManager.completeCalibration();
		if (onCalibrationComplete) {
			onCalibrationComplete();
		}
	}

	function skipCalibration() {
		calibrationManager.skipCalibration();
		if (onCalibrationComplete) {
			onCalibrationComplete();
		}
	}

	async function usePredefinedCalibration() {
		try {
			await calibrationManager.setPredefinedCalibration();
			if (onCalibrationComplete) {
				onCalibrationComplete();
			}
		} catch (error) {
			console.error("Failed to set predefined calibration:", error);
			// The error will be handled by the parent component's error handling
		}
	}

	function handleCancel() {
		if (isCalibrating) {
			calibrationManager.cancelCalibration();
		}
		if (onCancel) {
			onCancel();
		}
	}
</script>

<div class="space-y-4">
	{#if isCalibrating}
		<!-- Calibrating State -->
		<div class="space-y-3">
			<div class="flex items-center justify-between">
				<div class="flex items-center gap-2">
					<div class="h-2 w-2 animate-pulse rounded-full bg-green-500"></div>
					<span class="text-sm font-medium text-green-300">Recording movements...</span>
					<Badge variant="secondary" class="text-xs">{Math.round(progress)}%</Badge>
				</div>
				<div class="flex gap-2">
					<Button variant="default" size="sm" onclick={completeCalibration}>Complete</Button>
					<Button variant="outline" size="sm" onclick={handleCancel}>Cancel</Button>
				</div>
			</div>

			<!-- Compact progress bar -->
			<div class="h-1.5 w-full rounded-full bg-slate-700">
				<div
					class="h-1.5 rounded-full bg-green-500 transition-all duration-100"
					style="width: {progress}%"
				></div>
			</div>

			<!-- Compact joint grid with scrollable area -->
			<div class="max-h-48 overflow-y-auto">
				<div class="grid grid-cols-2 gap-2">
					{#each jointNames as jointName}
						{@const currentValue = calibrationManager.calibrationState.getCurrentValue(jointName)}
						{@const calibration = calibrationManager.calibrationState.getJointCalibration(jointName)}

						<div class="space-y-1 rounded bg-slate-700/50 p-2">
							<div class="flex items-center justify-between">
								<span class="text-xs font-medium text-slate-300">{jointName}</span>
								<span class="font-mono text-xs text-green-400"
									>{calibrationManager.calibrationState.formatServoValue(currentValue)}</span
								>
							</div>

							<div class="flex justify-between text-xs text-slate-500">
								<span>Min: {calibrationManager.calibrationState.formatServoValue(calibration?.minServoValue)}</span>
								<span>Max: {calibrationManager.calibrationState.formatServoValue(calibration?.maxServoValue)}</span>
							</div>

							{#if calibration?.minServoValue !== undefined && calibration?.maxServoValue !== undefined && currentValue !== undefined}
								<div class="relative h-1 overflow-hidden rounded-full bg-slate-600">
									<div
										class="absolute top-0 left-0 h-full bg-green-500 transition-all duration-50"
										style="width: {Math.max(
											0,
											Math.min(
												100,
												((currentValue - calibration.minServoValue) /
													(calibration.maxServoValue - calibration.minServoValue)) *
													100
											)
										)}%"
									></div>
								</div>
							{/if}
						</div>
					{/each}
				</div>
			</div>

			<div class="text-xs text-slate-500">Move each joint through its full range of motion</div>
		</div>
	{:else if isCalibrated}
		<!-- Calibrated State -->
		<div class="space-y-3">
			<div class="flex items-center gap-2">
				<Badge variant="default" class="bg-green-600">βœ“ Calibrated</Badge>
				<span class="text-sm text-green-300">Ready to connect</span>
			</div>

			<!-- Servo lock status -->
			<div class="rounded-lg border border-slate-600 bg-slate-700/30 p-3">
				<div class="mb-1 flex items-center gap-2">
					<span class="text-sm font-medium text-slate-300">{connectionInfo.lockStatus}</span>
				</div>
				<div class="text-xs text-slate-500">{connectionInfo.lockDescription}</div>
			</div>

			<!-- Compact calibration summary -->
			<div class="max-h-32 overflow-y-auto">
				<div class="grid grid-cols-2 gap-1">
					{#each jointNames as jointName}
						{@const calibration = calibrationManager.calibrationState.getJointCalibration(jointName)}
						{@const range = calibrationManager.calibrationState.getJointRange(jointName)}

						<div class="flex items-center justify-between rounded bg-slate-700/30 p-2 text-xs">
							<span class="font-medium text-slate-300">{jointName}</span>
							<span class="font-mono text-slate-400">{range}</span>
						</div>
					{/each}
				</div>
			</div>

			<div class="flex gap-2">
				<Button onclick={() => onCalibrationComplete?.()} class="flex-1" size="sm">
					Connect {connectionType === "producer" ? "Producer" : "Consumer"}
				</Button>
				<Button variant="outline" size="sm" onclick={startCalibration}>Redo</Button>
			</div>
		</div>
	{:else}
		<!-- Initial State - Need Calibration -->
		<div class="space-y-3">
			<div class="flex items-center gap-2">
				<Badge variant="destructive">Needs Calibration</Badge>
				<span class="text-sm text-slate-300">Required for USB connection</span>
			</div>

			<!-- Compact instructions -->
			<div class="rounded-lg border border-yellow-500/30 bg-yellow-900/20 p-3">
				<div class="mb-2 text-sm text-yellow-300">Quick Setup Options:</div>
				<ol class="space-y-1 text-xs text-yellow-200">
					<li>1. Position robot in neutral pose</li>
					<li>2. Start calibration and move each joint fully</li>
					<li>3. Complete when all joints show good ranges</li>
				</ol>
			</div>

			<!-- Connection type info -->
			<div class="rounded-lg border border-slate-600 bg-slate-700/30 p-3">
				<div class="mb-1 text-sm font-medium text-slate-300">After calibration:</div>
				<div class="text-xs text-slate-500">{connectionInfo.lockStatus}</div>
			</div>

			<div class="flex gap-2">
				<Button onclick={startCalibration} class="flex-1" size="sm">Start Calibration</Button>
				<Button variant="secondary" size="sm" onclick={usePredefinedCalibration}>Use Preset</Button>
				<Button variant="outline" size="sm" onclick={skipCalibration}>Skip</Button>
			</div>
		</div>
	{/if}
</div>