File size: 4,387 Bytes
8eeb37a
f62f94b
6ce4ca6
 
f62f94b
6ce4ca6
f62f94b
 
 
6ce4ca6
f62f94b
 
6ce4ca6
 
 
f62f94b
 
 
 
 
 
 
6ce4ca6
 
 
f62f94b
 
6ce4ca6
 
 
f62f94b
6ce4ca6
 
 
f62f94b
 
 
 
 
 
6ce4ca6
f62f94b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ce4ca6
 
 
f62f94b
 
 
 
6ce4ca6
 
 
f62f94b
6ce4ca6
 
 
 
 
 
 
 
 
 
 
 
f62f94b
 
6ce4ca6
 
 
 
f62f94b
 
 
6ce4ca6
f62f94b
 
 
 
 
 
6ce4ca6
f62f94b
 
 
6ce4ca6
f62f94b
6ce4ca6
f62f94b
 
6ce4ca6
f62f94b
 
 
 
6ce4ca6
f62f94b
 
 
 
 
 
 
 
 
 
6ce4ca6
f62f94b
 
 
6ce4ca6
 
 
 
 
 
 
 
f62f94b
6ce4ca6
 
 
f62f94b
 
6ce4ca6
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);
      }
    });
  }


}