# qnbot_replay

ROS2 Action Server for replaying `.msgpack` recorded files to WebSocket.

## Features

- Receives replay requests via ROS2 Action interface
- Supports timeline reproduction mode (sends at original recorded time intervals)
- Supports speed adjustment (fast/slow playback)
- Provides real-time progress feedback
- Supports cancelling ongoing replay tasks

## Dependency Installation

```bash
pip install msgpack websocket-client
```

## File Placement

Place `.msgpack` recorded files in the `recordings/` directory, or specify a custom directory via parameter.

## Usage

### 1. Start Action Server

```bash
ros2 run qnbot_replay replay_action_server
```

Or with a custom recordings directory:

```bash
ros2 run qnbot_replay replay_action_server --ros-args -p recordings_dir:=/path/to/recordings
```

### 2. Send Replay Request

Minimal usage (only file name, using default speed and WebSocket address):

```bash
ros2 action send_goal /replay_msgpack qnbot_replay/action/ReplayMsgpack \
  "{file_name: 'data.msgpack'}"
```

Specify speed (2x):

```bash
ros2 action send_goal /replay_msgpack qnbot_replay/action/ReplayMsgpack \
  "{file_name: 'data.msgpack', speed: 2.0}"
```

Full parameters + progress feedback:

```bash
ros2 action send_goal /replay_msgpack qnbot_replay/action/ReplayMsgpack \
  "{file_name: 'data.msgpack', speed: 1.0, ws_url: 'ws://127.0.0.1:19091'}" --feedback
```

### 3. Cancel Replay

```bash
# Get goal ID
ros2 action list

# Cancel specified goal
ros2 action cancel /replay_msgpack
```

## Action Interface Definition

### Goal (Request Parameters)
- `file_name`: .msgpack file name (without path) [Required]
- `speed`: Time scale factor (>1 speeds up, <1 slows down, **default 1.0**)
- `ws_url`: WebSocket server address (**default ws://127.0.0.1:19091**)

### Result
- `success`: Whether completed successfully
- `message`: Result description
- `total_packets`: Total number of packets
- `success_count`: Successfully sent packets
- `failed_count`: Failed packets
- `duration_seconds`: Actual replay duration (seconds)

### Feedback (Progress)
- `current_packet`: Current packet index sent
- `total_packets`: Total number of packets
- `progress_percent`: Progress percentage
- `success_count`: Packets sent successfully so far
- `failed_count`: Packets failed so far

## Example Code (Python Client)

```python
import rclpy
from rclpy.action import ActionClient
from rclpy.node import Node
from qnbot_replay.action import ReplayMsgpack

class ReplayClient(Node):
    def __init__(self):
        super().__init__('replay_client')
        self._action_client = ActionClient(self, ReplayMsgpack, 'replay_msgpack')
    
    def send_goal(self, file_name: str, speed: float = 1.0, ws_url: str = 'ws://127.0.0.1:19091'):
        goal_msg = ReplayMsgpack.Goal()
        goal_msg.file_name = file_name
        goal_msg.speed = speed
        goal_msg.ws_url = ws_url
        
        self._action_client.wait_for_server()
        
        self._send_goal_future = self._action_client.send_goal_async(
            goal_msg,
            feedback_callback=self.feedback_callback
        )
        self._send_goal_future.add_done_callback(self.goal_response_callback)
    
    def feedback_callback(self, feedback_msg):
        feedback = feedback_msg.feedback
        self.get_logger().info(
            f'Progress: {feedback.progress_percent:.1f}% '
            f'({feedback.current_packet}/{feedback.total_packets})'
        )
    
    def goal_response_callback(self, future):
        goal_handle = future.result()
        if not goal_handle.accepted:
            self.get_logger().info('Goal rejected')
            return
        
        self.get_logger().info('Goal accepted')
        self._get_result_future = goal_handle.get_result_async()
        self._get_result_future.add_done_callback(self.get_result_callback)
    
    def get_result_callback(self, future):
        result = future.result().result
        self.get_logger().info(f'Result: success={result.success}, message={result.message}')
        rclpy.shutdown()

def main():
    rclpy.init()
    client = ReplayClient()
    client.send_goal('recording.msgpack', speed=1.0)
    rclpy.spin(client)

if __name__ == '__main__':
    main()
```
