Spaces:
Running
Running
/** | |
* Direct teleoperator for Web platform | |
* Handles programmatic motor control (sliders, API calls, etc.) | |
*/ | |
import { | |
BaseWebTeleoperator, | |
type TeleoperatorSpecificState, | |
} from "./base-teleoperator.js"; | |
import type { | |
DirectTeleoperatorConfig, | |
MotorConfig, | |
TeleoperationState, | |
} from "../types/teleoperation.js"; | |
import type { MotorCommunicationPort } from "../utils/motor-communication.js"; | |
import { | |
readMotorPosition, | |
writeMotorPosition, | |
} from "../utils/motor-communication.js"; | |
export class DirectTeleoperator extends BaseWebTeleoperator { | |
private onStateUpdate?: (state: TeleoperationState) => void; | |
constructor( | |
config: DirectTeleoperatorConfig, | |
port: MotorCommunicationPort, | |
motorConfigs: MotorConfig[], | |
onStateUpdate?: (state: TeleoperationState) => void | |
) { | |
super(port, motorConfigs); | |
this.onStateUpdate = onStateUpdate; | |
} | |
async initialize(): Promise<void> { | |
// Read current motor positions | |
for (const config of this.motorConfigs) { | |
const position = await readMotorPosition(this.port, config.id); | |
if (position !== null) { | |
config.currentPosition = position; | |
} | |
} | |
} | |
start(): void { | |
this.isActive = true; | |
} | |
stop(): void { | |
this.isActive = false; | |
// Notify UI of state change | |
if (this.onStateUpdate) { | |
this.onStateUpdate(this.buildTeleoperationState()); | |
} | |
} | |
getState(): TeleoperatorSpecificState { | |
return {}; | |
} | |
/** | |
* Move motor to exact position | |
*/ | |
async moveMotor(motorName: string, targetPosition: number): Promise<boolean> { | |
const motorConfig = this.motorConfigs.find((m) => m.name === motorName); | |
if (!motorConfig) return false; | |
const clampedPosition = Math.max( | |
motorConfig.minPosition, | |
Math.min(motorConfig.maxPosition, targetPosition) | |
); | |
try { | |
await writeMotorPosition( | |
this.port, | |
motorConfig.id, | |
Math.round(clampedPosition) | |
); | |
motorConfig.currentPosition = clampedPosition; | |
// Notify UI of position change | |
if (this.onStateUpdate) { | |
this.onStateUpdate(this.buildTeleoperationState()); | |
} | |
return true; | |
} catch (error) { | |
console.warn(`Failed to move motor ${motorName}:`, error); | |
return false; | |
} | |
} | |
/** | |
* Set multiple motor positions at once | |
*/ | |
async setMotorPositions(positions: { | |
[motorName: string]: number; | |
}): Promise<boolean> { | |
const results = await Promise.all( | |
Object.entries(positions).map(([motorName, position]) => | |
this.moveMotor(motorName, position) | |
) | |
); | |
return results.every((result) => result); | |
} | |
private buildTeleoperationState(): TeleoperationState { | |
return { | |
isActive: this.isActive, | |
motorConfigs: [...this.motorConfigs], | |
lastUpdate: Date.now(), | |
}; | |
} | |
} | |