From 254805008ea94857720c346e201051a285ddeb6c Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Wed, 1 Apr 2026 17:09:33 -0400 Subject: [PATCH] feat: add gesture sequence support in wizard-ros-service - Add transformToGestureSequence for multi-movement gestures - Update executeWithConfig to handle gesture sequences - Add sequence execution with delays between movements - Fix experiment description to be optional --- robot-plugins | 2 +- src/lib/ros/wizard-ros-service.ts | 79 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/robot-plugins b/robot-plugins index 442be9f..19e3858 160000 --- a/robot-plugins +++ b/robot-plugins @@ -1 +1 @@ -Subproject commit 442be9f6c6d5e774bb8230614c0924d996cdc4f1 +Subproject commit 19e38589c5b7e0fc3df313539a3dd2a48d137ed4 diff --git a/src/lib/ros/wizard-ros-service.ts b/src/lib/ros/wizard-ros-service.ts index 4a01133..e3b255a 100644 --- a/src/lib/ros/wizard-ros-service.ts +++ b/src/lib/ros/wizard-ros-service.ts @@ -659,6 +659,47 @@ export class WizardRosService extends EventEmitter { msg = parameters; } + // Handle gesture sequences - send multiple movements with delays + if ((msg as any).type === "gesture_sequence") { + const sequence = (msg as any).movements as Array<{ + joint_names: string[]; + joint_angles: number[]; + speed: number; + }>; + + if (!sequence || sequence.length === 0) { + console.warn("[WizardROS] Gesture sequence has no movements"); + return; + } + + console.log(`[WizardROS] Executing gesture sequence with ${sequence.length} movements`); + + for (let i = 0; i < sequence.length; i++) { + const movement = sequence[i]; + if (!movement) continue; + + this.publish( + "/joint_angles", + "naoqi_bridge_msgs/msg/JointAnglesWithSpeed", + { + joint_names: movement.joint_names, + joint_angles: movement.joint_angles, + speed: movement.speed || 0.3, + } + ); + + // Delay between movements + const delay = movement.speed ? Math.max(300, 1000 / movement.speed) : 800; + if (i < sequence.length - 1) { + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + + // Wait for final movement to complete + await new Promise((resolve) => setTimeout(resolve, 1000)); + return; + } + this.publish(config.topic, config.messageType, msg); // Wait for action completion based on topic type @@ -1048,6 +1089,12 @@ export class WizardRosService extends EventEmitter { case "transformToAnimation": return this.transformToAnimation(parameters); + case "transformToGestureSequence": + return this.transformToGestureSequence(parameters); + + case "transformToWaveGoodbye": + return this.transformToWaveGoodbye(parameters); + default: console.warn(`Unknown transform function: ${transformFn}`); return parameters; @@ -1113,6 +1160,38 @@ export class WizardRosService extends EventEmitter { return { data: markedText }; } + /** + * Transform for gesture sequences - sends multiple joint angle movements + * Parameters: movements = [{joints: string[], angles: number[], duration: number}, ...] + */ + private transformToGestureSequence(parameters: Record): { + type: string; + movements: Array<{ + joint_names: string[]; + joint_angles: number[]; + speed: number; + }>; + } { + const movements = parameters.movements as Array<{ + joints?: string[]; + angles?: number[]; + duration?: number; + speed?: number; + }>; + + if (!Array.isArray(movements)) { + return { type: "gesture_sequence", movements: [] }; + } + + const parsedMovements = movements.map((m, index) => ({ + joint_names: m.joints || [], + joint_angles: m.angles || [], + speed: m.speed || (m.duration ? 1 / (m.duration / 1000) : 0.3), + })); + + return { type: "gesture_sequence", movements: parsedMovements }; + } + /** * Schedule reconnection attempt */