fix(trial-execution): handle SSH actions without requiring ROS connection

- Add startTime parameter to executeRobotActionInternal for proper duration tracking
- SSH actions (animations, posture commands) now work without ROS bridge connection
- Refactor executeAction to handle SSH and ROS paths separately
This commit is contained in:
2026-04-01 19:44:08 -04:00
parent 27f633fb4b
commit 3e2aa894a0
+70 -51
View File
@@ -181,41 +181,71 @@ export class RobotCommunicationService extends EventEmitter {
* Execute a robot action * Execute a robot action
*/ */
async executeAction(action: RobotAction): Promise<RobotActionResult> { async executeAction(action: RobotAction): Promise<RobotActionResult> {
const actionId = `action_${this.messageId++}`;
const startTime = Date.now();
// Check if this is an SSH-only action (animations, posture, arbitrary SSH commands)
const { implementation, actionId: actionType } = action;
const baseActionId = actionType.includes(".")
? actionType.split(".").pop()
: actionType;
const isAnimationAction = baseActionId?.startsWith("play_animation_");
const sshCommand = implementation.payloadMapping?.sshCommand
|| implementation.ros2?.payloadMapping?.sshCommand;
// SSH actions don't require ROS connection
if (isAnimationAction || sshCommand) {
const timeout = setTimeout(() => {
throw new Error(`SSH action timeout: ${action.actionId}`);
}, 30000);
try {
console.log(`[RobotComm] Executing SSH action: ${action.actionId}`);
const result = await this.executeRobotActionInternal(action, actionId, startTime);
clearTimeout(timeout);
return result;
} catch (error) {
clearTimeout(timeout);
throw error;
}
}
// Non-SSH actions require ROS connection
if (!this.isConnected) { if (!this.isConnected) {
throw new Error("Not connected to ROS bridge"); throw new Error("Not connected to ROS bridge");
} }
const startTime = Date.now();
const actionId = `action_${this.messageId++}`;
return new Promise((resolve, reject) => {
// Set up timeout
const timeout = setTimeout(() => {
this.pendingActions.delete(actionId);
reject(new Error(`Action timeout: ${action.actionId}`));
}, 30000); // 30 second timeout
// Store pending action // Store pending action
this.pendingActions.set(actionId, { const pending = {
resolve, resolve: (() => {}) as (result: RobotActionResult) => void,
reject, reject: (() => {}) as (error: Error) => void,
timeout, timeout: setTimeout(() => {
this.pendingActions.delete(actionId);
pending.reject(new Error(`Action timeout: ${action.actionId}`));
}, 30000),
startTime, startTime,
}); };
try { this.pendingActions.set(actionId, pending);
// Log the action we're about to execute
console.log(`[RobotComm] Executing robot action: ${action.actionId}`);
console.log(`[RobotComm] Topic: ${action.implementation.topic}`);
console.log(`[RobotComm] Parameters:`, action.parameters);
// Execute action based on type and platform // Wrap the pending resolve/reject in a way that works with async method
this.executeRobotActionInternal(action, actionId); return new Promise<RobotActionResult>((resolve, reject) => {
} catch (error) { pending.resolve = resolve;
clearTimeout(timeout); pending.reject = reject;
// Execute action
this.executeRobotActionInternal(action, actionId, startTime)
.then((result) => {
clearTimeout(pending.timeout);
this.pendingActions.delete(actionId);
resolve(result);
})
.catch((error) => {
clearTimeout(pending.timeout);
this.pendingActions.delete(actionId); this.pendingActions.delete(actionId);
reject(error); reject(error);
} });
}); });
} }
@@ -228,10 +258,11 @@ export class RobotCommunicationService extends EventEmitter {
// Private methods // Private methods
private executeRobotActionInternal( private async executeRobotActionInternal(
action: RobotAction, action: RobotAction,
actionId: string, actionId: string,
): void { startTime: number,
): Promise<RobotActionResult> {
const { implementation, parameters, actionId: actionType } = action; const { implementation, parameters, actionId: actionType } = action;
// Use SSH for play_animation actions (check both namespaced and non-namespaced) // Use SSH for play_animation actions (check both namespaced and non-namespaced)
@@ -240,18 +271,12 @@ export class RobotCommunicationService extends EventEmitter {
: actionType; : actionType;
if (baseActionId?.startsWith("play_animation_")) { if (baseActionId?.startsWith("play_animation_")) {
this.executeAnimationViaSSH(baseActionId).then(() => { await this.executeAnimationViaSSH(baseActionId);
this.completeAction(actionId, { return {
success: true, success: true,
duration: duration: Date.now() - startTime,
Date.now() -
(this.pendingActions.get(actionId)?.startTime || Date.now()),
data: { method: "ssh", action: baseActionId }, data: { method: "ssh", action: baseActionId },
}); };
}).catch((error) => {
this.pendingActions.get(actionId)?.reject(error);
});
return;
} }
// Check for SSH command type // Check for SSH command type
@@ -259,18 +284,12 @@ export class RobotCommunicationService extends EventEmitter {
|| implementation.ros2?.payloadMapping?.sshCommand; || implementation.ros2?.payloadMapping?.sshCommand;
if (sshCommand) { if (sshCommand) {
this.executeSSHCommand(sshCommand).then(() => { await this.executeSSHCommand(sshCommand);
this.completeAction(actionId, { return {
success: true, success: true,
duration: duration: Date.now() - startTime,
Date.now() -
(this.pendingActions.get(actionId)?.startTime || Date.now()),
data: { method: "ssh", command: sshCommand }, data: { method: "ssh", command: sshCommand },
}); };
}).catch((error) => {
this.pendingActions.get(actionId)?.reject(error);
});
return;
} }
// Apply transform if specified // Apply transform if specified
@@ -297,12 +316,11 @@ export class RobotCommunicationService extends EventEmitter {
// For actions that complete immediately (like movement commands), // For actions that complete immediately (like movement commands),
// we simulate completion after a short delay // we simulate completion after a short delay
return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
this.completeAction(actionId, { resolve({
success: true, success: true,
duration: duration: Date.now() - startTime,
Date.now() -
(this.pendingActions.get(actionId)?.startTime || Date.now()),
data: { data: {
topic: implementation.topic, topic: implementation.topic,
messageType: implementation.messageType, messageType: implementation.messageType,
@@ -310,6 +328,7 @@ export class RobotCommunicationService extends EventEmitter {
}, },
}); });
}, 100); }, 100);
});
} }
private async executeSSHCommand(command: string): Promise<void> { private async executeSSHCommand(command: string): Promise<void> {