File size: 4,015 Bytes
67a499d
 
 
6ce4ca6
f62f94b
67a499d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import type { Consumer, RobotCommand } from "../models.js";
import { USBServoDriver } from "./USBServoDriver.js";
import { ROBOT_CONFIG } from "../config.js";

export class USBConsumer extends USBServoDriver implements Consumer {
	private commandCallbacks: ((command: RobotCommand) => void)[] = [];
	private pollingInterval: ReturnType<typeof setInterval> | null = null;
	private lastPositions: Record<number, number> = {};
	private errorCount = 0;

	constructor(config: any) {
		super(config, "Consumer");
	}

	async connect(): Promise<void> {
		// Connect to USB first (this triggers browser's device selection dialog)
		await this.connectToUSB();

		// Unlock servos for manual movement (consumer mode)
		await this.unlockAllServos();

		// Note: Calibration is checked when operations are actually needed
	}

	async disconnect(): Promise<void> {
		await this.stopListening();
		await this.disconnectFromUSB();
	}

	async startListening(): Promise<void> {
		if (!this._status.isConnected || this.pollingInterval !== null) {
			return;
		}

		if (!this.isCalibrated) {
			throw new Error("Cannot start listening: not calibrated");
		}

		console.log(`[${this.name}] Starting position listening...`);
		this.errorCount = 0;

		this.pollingInterval = setInterval(async () => {
			try {
				await this.pollAndBroadcastPositions();
				this.errorCount = 0;
			} catch (error) {
				this.errorCount++;
				console.warn(`[${this.name}] Polling error (${this.errorCount}):`, error);

				if (this.errorCount >= ROBOT_CONFIG.polling.maxPollingErrors) {
					console.warn(`[${this.name}] Too many polling errors, slowing down...`);
					await this.stopListening();
					setTimeout(() => this.startListening(), ROBOT_CONFIG.polling.errorBackoffRate);
				}
			}
		}, ROBOT_CONFIG.polling.consumerPollingRate);
	}

	async stopListening(): Promise<void> {
		if (this.pollingInterval !== null) {
			clearInterval(this.pollingInterval);
			this.pollingInterval = null;
			console.log(`[${this.name}] Stopped position listening`);
		}
	}

	// Event handlers already in base class

	onCommand(callback: (command: RobotCommand) => void): () => void {
		this.commandCallbacks.push(callback);
		return () => {
			const index = this.commandCallbacks.indexOf(callback);
			if (index >= 0) {
				this.commandCallbacks.splice(index, 1);
			}
		};
	}

	// Private methods
	private async pollAndBroadcastPositions(): Promise<void> {
		if (!this.scsServoSDK || !this._status.isConnected) {
			return;
		}

		try {
			// Read positions for all servos
			const servoIds = Object.values(this.jointToServoMap);
			const positions = await this.scsServoSDK.syncReadPositions(servoIds);

			const jointsWithChanges: { name: string; value: number }[] = [];

			// Check for position changes and convert to normalized values
			Object.entries(this.jointToServoMap).forEach(([jointName, servoId]) => {
				const currentPosition = positions.get(servoId);
				const lastPosition = this.lastPositions[servoId];

				if (
					currentPosition !== undefined &&
					(lastPosition === undefined ||
						Math.abs(currentPosition - lastPosition) >
							ROBOT_CONFIG.performance.jointUpdateThreshold)
				) {
					this.lastPositions[servoId] = currentPosition;

					// Convert to normalized value using calibration (required)
					const normalizedValue = this.normalizeValue(currentPosition, jointName);

					jointsWithChanges.push({
						name: jointName,
						value: normalizedValue
					});
				}
			});

			// Broadcast changes if any
			if (jointsWithChanges.length > 0) {
				const command: RobotCommand = {
					timestamp: Date.now(),
					joints: jointsWithChanges
				};

				this.notifyCommand(command);
			}
		} catch (error) {
			throw error; // Re-throw for error handling in polling loop
		}
	}

	private notifyCommand(command: RobotCommand): void {
		this.commandCallbacks.forEach((callback) => {
			try {
				callback(command);
			} catch (error) {
				console.error(`[${this.name}] Error in command callback:`, error);
			}
		});
	}
}