feat(tutorials): add comprehensive tutorials for HRIStudio including Getting Started, Your First Study, Designing Experiments, Running Trials, Wizard Interface, Robot Integration, Forms & Surveys, Data & Analysis, and Simulation Mode

This commit is contained in:
2026-03-25 22:48:42 -04:00
parent 3959cf23f7
commit 1c7f0297a6
34 changed files with 6298 additions and 139 deletions
+1
View File
@@ -202,6 +202,7 @@ src/
Comprehensive documentation available in the `docs/` folder: Comprehensive documentation available in the `docs/` folder:
- **[Tutorials](docs/tutorials/README.md)**: Step-by-step guides for new users
- **[Quick Reference](docs/quick-reference.md)**: Essential commands and setup - **[Quick Reference](docs/quick-reference.md)**: Essential commands and setup
- **[Implementation Guide](docs/implementation-guide.md)**: Technical implementation details - **[Implementation Guide](docs/implementation-guide.md)**: Technical implementation details
- **[Project Status](docs/project-status.md)**: Current development state - **[Project Status](docs/project-status.md)**: Current development state
+30
View File
@@ -6,11 +6,28 @@ HRIStudio is a web-based Wizard-of-Oz platform for Human-Robot Interaction resea
| Document | Description | | Document | Description |
|----------|-------------| |----------|-------------|
| **[Tutorials](tutorials/README.md)** | Step-by-step guides for using HRIStudio |
| **[Quick Reference](quick-reference.md)** | Essential commands, setup, troubleshooting | | **[Quick Reference](quick-reference.md)** | Essential commands, setup, troubleshooting |
| **[Project Status](project-status.md)** | Current development state (March 2026) | | **[Project Status](project-status.md)** | Current development state (March 2026) |
| **[Implementation Guide](implementation-guide.md)** | Full technical implementation | | **[Implementation Guide](implementation-guide.md)** | Full technical implementation |
| **[NAO6 Integration](nao6-quick-reference.md)** | Robot setup and commands | | **[NAO6 Integration](nao6-quick-reference.md)** | Robot setup and commands |
## Tutorials
New to HRIStudio? Start with our comprehensive tutorials:
| Tutorial | Description | Time |
|----------|-------------|------|
| [Getting Started](tutorials/01-getting-started.md) | Installation and first login | 10 min |
| [Your First Study](tutorials/02-your-first-study.md) | Creating a research study | 15 min |
| [Designing Experiments](tutorials/03-designing-experiments.md) | Building experiment protocols | 25 min |
| [Running Trials](tutorials/04-running-trials.md) | Executing experiments | 20 min |
| [Wizard Interface](tutorials/05-wizard-interface.md) | Real-time trial control | 15 min |
| [Robot Integration](tutorials/06-robot-integration.md) | Connecting NAO6 robot | 20 min |
| [Forms & Surveys](tutorials/07-forms-and-surveys.md) | Managing consent and data | 15 min |
| [Data & Analysis](tutorials/08-data-and-analysis.md) | Collecting and exporting data | 15 min |
| [Simulation Mode](tutorials/09-simulation-mode.md) | Testing without a robot | 10 min |
## Getting Started ## Getting Started
### 1. Clone & Install ### 1. Clone & Install
@@ -162,9 +179,22 @@ bun db:seed
- `docs/quick-reference.md` - Commands & setup - `docs/quick-reference.md` - Commands & setup
- `docs/nao6-quick-reference.md` - NAO6 commands - `docs/nao6-quick-reference.md` - NAO6 commands
### Tutorials
- `docs/tutorials/README.md` - Tutorial overview
- `docs/tutorials/01-getting-started.md` - Installation & setup
- `docs/tutorials/02-your-first-study.md` - Creating studies
- `docs/tutorials/03-designing-experiments.md` - Building protocols
- `docs/tutorials/04-running-trials.md` - Executing trials
- `docs/tutorials/05-wizard-interface.md` - Trial control
- `docs/tutorials/06-robot-integration.md` - Robot setup
- `docs/tutorials/07-forms-and-surveys.md` - Forms management
- `docs/tutorials/08-data-and-analysis.md` - Data collection
- `docs/tutorials/09-simulation-mode.md` - Testing without robot
### Technical Documentation ### Technical Documentation
- `docs/implementation-guide.md` - Full technical implementation - `docs/implementation-guide.md` - Full technical implementation
- `docs/project-status.md` - Development status - `docs/project-status.md` - Development status
- `docs/mock-robot-simulation.md` - Robot simulation
### Archive (Historical) ### Archive (Historical)
- `docs/_archive/` - Old documentation (outdated but preserved) - `docs/_archive/` - Old documentation (outdated but preserved)
+182
View File
@@ -0,0 +1,182 @@
# HRIStudio Mock Robot Simulation
This directory contains a mock robot server for simulating NAO6 robot connections without a physical robot.
## Quick Start
### Option 1: Standalone Mock Server (Recommended for testing)
```bash
cd scripts/mock-robot
bun install
bun dev
```
This starts the mock robot WebSocket server on `ws://localhost:9090`.
### Option 2: Docker Compose Mock Mode
```bash
cd nao6-hristudio-integration
docker compose -f docker-compose.yml -f docker-compose.mock.yml --profile mock up -d
```
### Option 3: Client-Side Simulation (No server needed)
Enable simulation mode in the wizard interface:
- Set `NEXT_PUBLIC_SIMULATION_MODE=true` in your `.env` file
- Or use the simulation toggle in the UI
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ HRIStudio Platform │
├─────────────────────────────────────────────────────────────┤
│ Wizard Interface (Browser) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ wizard-ros-service.ts │ │
│ │ ├── simulationMode: true → Simulates locally │ │
│ │ └── simulationMode: false → Connects to server │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ │ │ │
│ Real Mode Simulation Mode │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Mock Robot │ │ Local JS │ │
│ │ WebSocket │ │ Simulation │ │
│ │ Server │ │ (No server) │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Mock Robot Server Protocol
The mock server implements the rosbridge WebSocket protocol:
### Supported Operations
| Operation | Description |
|-----------|-------------|
| `subscribe` | Subscribe to a topic |
| `unsubscribe` | Unsubscribe from a topic |
| `publish` | Publish a message to a topic |
| `call_service` | Call a ROS service |
| `advertise` | Advertise a topic |
| `unadvertise` | Stop advertising a topic |
### Simulated Topics
| Topic | Type | Description |
|-------|------|-------------|
| `/joint_states` | `sensor_msgs/JointState` | Joint positions (26 joints) |
| `/naoqi_driver/battery` | `naoqi_bridge_msgs/Battery` | Battery status (85%) |
| `/bumper` | `naoqi_bridge_msgs/Bumper` | Bumper contact sensors |
| `/hand_touch` | `naoqi_bridge_msgs/HandTouch` | Hand touch sensors |
| `/head_touch` | `naoqi_bridge_msgs/HeadTouch` | Head touch sensors |
| `/sonar/left` | `sensor_msgs/Range` | Left sonar distance |
| `/sonar/right` | `sensor_msgs/Range` | Right sonar distance |
### Simulated Services
| Service | Response |
|---------|----------|
| `/naoqi_driver/get_robot_info` | `{ robotName: "MOCK-NAO6", robotVersion: "6.0" }` |
| `/naoqi_driver/get_joint_names` | List of 26 joint names |
| `/naoqi_driver/get_position` | Current position `{ x, y, theta }` |
| `/naoqi_driver/is_waking_up` | `{ is_waking_up: false }` |
### Supported Actions
| Action | Parameters | Description |
|--------|------------|-------------|
| `say_text` | `text` | Speak text |
| `walk_forward` | `speed` | Walk forward |
| `walk_backward` | `speed` | Walk backward |
| `turn_left` | `speed` | Turn left |
| `turn_right` | `speed` | Turn right |
| `stop` | - | Stop all movement |
| `move_head` | `yaw`, `pitch`, `speed` | Move head |
## Configuration
### Environment Variables
```bash
# Mock Robot Server (scripts/mock-robot)
MOCK_ROBOT_PORT=9090 # WebSocket port
MOCK_PUBLISH_INTERVAL=100 # Sensor update interval (ms)
# HRIStudio Client
NEXT_PUBLIC_SIMULATION_MODE=true # Enable client-side simulation
NEXT_PUBLIC_ROS_BRIDGE_URL=ws://localhost:9090
```
## Testing
### 1. Start Mock Server
```bash
cd scripts/mock-robot
bun dev
```
### 2. Start HRIStudio
```bash
cd hristudio
bun dev
```
### 3. Test Connection
Visit `http://localhost:3000/nao-test` and click "Connect". You should see:
- Connection status: `connected`
- Battery: ~85%
- Joint states updating
- Log messages showing subscriptions
### 4. Test Actions
Use the wizard interface to test:
- Speech actions
- Movement actions
- Head control
## Troubleshooting
### "Connection timeout" error
- Ensure mock server is running: `curl ws://localhost:9090`
- Check port is correct (default 9090)
### "Not connected to ROS bridge" error
- Enable simulation mode: `NEXT_PUBLIC_SIMULATION_MODE=true`
- Or connect to mock server first
### Actions not executing
- Check connection status in wizard interface
- Enable simulation mode if using client-side simulation
## Files
| File | Description |
|------|-------------|
| `scripts/mock-robot/src/server.ts` | TypeScript mock server |
| `scripts/mock-robot/server.js` | JavaScript mock server (for Docker) |
| `src/lib/ros/wizard-ros-service.ts` | Client with simulation mode |
| `src/hooks/useWizardRos.ts` | React hook with simulation support |
| `docker-compose.mock.yml` | Docker mock service |
| `robot-plugins/plugins/nao6-mock.json` | Mock NAO6 plugin |
## Development
### Adding New Simulated Actions
1. Edit `scripts/mock-robot/src/server.ts`
2. Add handler in `handlePublish()` or `handleServiceCall()`
3. Update `nao6-mock.json` plugin with new action definition
### Adding New Simulated Sensors
1. Edit `scripts/mock-robot/src/server.ts`
2. Add topic publishing in `publishRobotState()`
3. Update subscriber topics in `WizardRosService.subscribeToRobotTopics()`
+151
View File
@@ -0,0 +1,151 @@
# Tutorial 1: Getting Started
Learn how to set up HRIStudio and log in for the first time.
## Objectives
- Install HRIStudio dependencies
- Start the development environment
- Log in and explore the interface
## Prerequisites
- [Bun](https://bun.sh) installed
- [Docker](https://docker.com) installed
- [Git](https://git-scm.com) installed
## Step 1: Clone the Repository
```bash
git clone https://github.com/soconnor0919/hristudio.git
cd hristudio
```
## Step 2: Install Dependencies
HRIStudio uses Bun as its package manager:
```bash
bun install
```
## Step 3: Start the Database
HRIStudio requires PostgreSQL. The easiest way is using Docker:
```bash
# Start PostgreSQL and MinIO (for file storage)
bun run docker:up
# Push database schema
bun db:push
# Seed with sample data
bun db:seed
```
This creates the database schema and populates it with:
- 4 default user accounts
- Sample study and experiments
- Test participants and trials
## Step 4: Start the Development Server
```bash
bun dev
```
The application will be available at `http://localhost:3000`.
## Step 5: Log In
Use one of the default accounts:
| Role | Email | Password |
|------|-------|----------|
| Administrator | `sean@soconnor.dev` | `password123` |
| Researcher | `felipe.perrone@bucknell.edu` | `password123` |
| Wizard | `emily.watson@lab.edu` | `password123` |
| Observer | `maria.santos@tech.edu` | `password123` |
## Exploring the Interface
After logging in, you'll see the main dashboard:
```
┌─────────────────────────────────────────────────────────────┐
│ HRIStudio [User] [Settings] │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Studies │ │ Trials │ │Plugins │ │ Admin │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Recent Activity │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • Study: Comparative WoZ Study - Ready │ │
│ │ • Trial: P101 - Completed (5 min ago) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Navigation
- **Studies** - View and manage your research studies
- **Trials** - Monitor and manage experiment trials
- **Plugins** - Manage robot integrations
- **Admin** - System administration (admins only)
## Using Simulation Mode
If you don't have a physical robot, enable simulation mode:
1. Create or edit `hristudio/.env.local`
2. Add: `NEXT_PUBLIC_SIMULATION_MODE=true`
3. Restart the dev server
Simulation mode allows you to test experiments without connecting to a real robot.
## Troubleshooting
### Database Connection Failed
```bash
# Check if Docker is running
docker ps
# Restart the database
bun run docker:down
bun run docker:up
bun db:push
```
### Port Already in Use
If port 3000 is in use:
```bash
# Use a different port
PORT=3001 bun dev
```
### Seed Script Fails
```bash
# Reset the database
bun run docker:down -v
bun run docker:up
bun db:push
bun db:seed
```
## Next Steps
Now that you're set up:
1. **[Your First Study](02-your-first-study.md)** - Create a research study
2. **[Designing Experiments](03-designing-experiments.md)** - Build your first protocol
3. **[Simulation Mode](09-simulation-mode.md)** - Test without a robot
---
**Previous**: [Tutorials Overview](../tutorials/README.md) | **Next**: [Your First Study](02-your-first-study.md)
+222
View File
@@ -0,0 +1,222 @@
# Tutorial 2: Your First Study
Learn how to create a research study and configure team access.
## Objectives
- Create a new research study
- Configure study settings (IRB, institution)
- Add team members with appropriate roles
## What is a Study?
In HRIStudio, a **Study** is the top-level container for your research:
```
Study
├── Experiments (multiple protocols)
├── Participants (study participants)
├── Team Members (collaborators)
├── Forms & Surveys (consent, questionnaires)
└── Trials (individual experiment runs)
```
## Step 1: Create a New Study
1. Log in as **Researcher** or **Administrator**
2. Click **Studies** in the sidebar
3. Click **Create Study**
### Study Settings
| Field | Description | Required |
|-------|-------------|----------|
| Name | Study title | Yes |
| Description | Brief overview of research goals | Yes |
| Institution | University or organization | No |
| IRB Protocol | Protocol number (e.g., 2024-HRI-001) | No |
| Status | Draft, Active, Completed, Archived | Yes |
### Example: Creating "Robot Trust Study"
```
Name: Robot Trust Study
Description: Investigating how robot appearance affects human trust in collaborative tasks.
Institution: Bucknell University
IRB Protocol: 2024-HRI-TRUST
Status: Draft
```
## Step 2: Add Team Members
Studies can have multiple collaborators with different roles:
| Role | Permissions |
|------|-------------|
| Owner | Full access, can delete study |
| Researcher | Create/edit experiments, manage participants |
| Wizard | Execute trials, control robot |
| Observer | View-only access, add annotations |
### Adding a Wizard
1. Open your study
2. Go to **Team** tab
3. Click **Add Member**
4. Enter the wizard's email
5. Select **Wizard** role
6. Click **Invite**
The wizard will receive access to:
- View the study and experiments
- Execute trials
- Control the robot during trials
- Add notes to trials
## Step 3: Install Robot Plugins
For studies involving robots, you need to install the appropriate plugin:
1. Go to **Plugins** in the sidebar
2. Select your study from the dropdown
3. Click **Browse Plugins**
4. Find your robot (e.g., "NAO6 Robot (ROS2 Integration)")
5. Click **Install**
6. Configure robot settings (IP address, etc.)
### Plugin Configuration
For NAO6 robots:
```
Robot IP: 192.168.1.100
Connection Type: ROS2 Bridge
WebSocket URL: ws://localhost:9090
```
## Step 4: Create Forms
Before running trials, you need consent forms:
1. Go to **Forms** tab in your study
2. Click **Create Form**
3. Select form type:
- **Consent** - Informed consent documents
- **Survey** - Post-session questionnaires
- **Questionnaire** - Demographic forms
### Form Templates
HRIStudio provides templates to get started:
| Template | Use Case |
|----------|----------|
| Informed Consent | Required for all participants |
| Post-Session Survey | Collect feedback after trials |
| Demographics | Collect participant information |
## Step 5: Add Participants
1. Go to **Participants** tab
2. Click **Add Participant**
3. Enter participant code (e.g., "P001")
4. Fill in optional details
### Batch Import
For large studies, import from CSV:
```csv
participantCode,name,email,notes
P001,John Smith,john@email.com,Condition A
P002,Jane Doe,jane@email.com,Condition B
```
## Study Workflow
```
Draft → Active → Recruiting → In Progress → Completed
│ │ │ │ │
│ │ │ │ └── All trials done
│ │ │ └── Trials running
│ │ └── Recruiting participants
│ └── Ready to collect data
└── Setting up study
```
## Study Settings Deep Dive
### IRB Compliance
Store your IRB information:
- Protocol number
- Approval date
- Expiration date
- Consent form versions
### Data Management
Configure data retention:
- Anonymization settings
- Export formats (CSV, JSON)
- Backup frequency
### Notification Settings
Configure alerts for:
- Trial completion
- Participant issues
- Robot disconnection
## Common Tasks
### Clone a Study
Create a copy of an existing study:
1. Open the study
2. Click **Settings** (gear icon)
3. Select **Duplicate Study**
4. Enter new study name
### Archive a Study
When a study is complete:
1. Go to study settings
2. Change status to **Archived**
3. Data is preserved but study is read-only
### Transfer Ownership
Change the study owner:
1. Go to **Team** tab
2. Find the new owner
3. Click **Make Owner**
## Troubleshooting
### Can't Add Team Member
- Check email is correct
- User must have an HRIStudio account
- You must be an owner or admin
### Plugin Installation Failed
- Check robot is on the network
- Verify WebSocket URL is correct
- Check Docker services are running
## Next Steps
Now that your study is set up:
1. **[Designing Experiments](03-designing-experiments.md)** - Create your first experiment protocol
2. **[Forms & Surveys](07-forms-and-surveys.md)** - Customize your consent forms
3. **[Running Trials](04-running-trials.md)** - Learn about trial management
---
**Previous**: [Getting Started](01-getting-started.md) | **Next**: [Designing Experiments](03-designing-experiments.md)
+341
View File
@@ -0,0 +1,341 @@
# Tutorial 3: Designing Experiments
Learn how to create experiment protocols using the visual block designer.
## Objectives
- Navigate the experiment designer
- Use core blocks (events, wizard actions, control flow)
- Build a branching experiment protocol
## What is an Experiment?
An **Experiment** defines the protocol for your study:
```
Experiment
├── Steps (ordered sequence)
│ ├── Actions (robot behaviors)
│ ├── Wizard Blocks (human decisions)
│ └── Control Flow (loops, branches)
├── Robot Actions (from plugins)
└── Parameters (configurable values)
```
## Step 1: Create an Experiment
1. Open your study
2. Go to **Experiments** tab
3. Click **New Experiment**
### Experiment Settings
| Field | Description |
|-------|-------------|
| Name | Protocol title |
| Description | What the experiment measures |
| Robot | Which robot to use |
| Version | Track protocol versions |
## Step 2: The Experiment Designer Interface
The designer has three main areas:
```
┌──────────────────────────────────────────────────────────────┐
│ Experiment: Robot Trust Study v1 [Save] │
├────────────┬─────────────────────────────────────────────────┤
│ │ │
│ Blocks │ Canvas │
│ Library │ │
│ │ ┌─────────┐ ┌─────────┐ │
│ ┌──────┐ │ │ Step 1 │───▶│ Step 2 │ │
│ │Events│ │ │ Hook │ │ Story │ │
│ ├──────┤ │ └─────────┘ └────┬────┘ │
│ │Wizard│ │ │ │
│ ├──────┤ │ ┌────▼────┐ │
│ │Control│ │ │ Step 3 │ │
│ ├──────┤ │ │ Check │ │
│ │Robot │ │ └────┬────┘ │
│ └──────┘ │ ┌────┴────┐ │
│ │ ┌────┴───┐ ┌───┴────┐ │
│ │ │Step 4a │ │Step 4b │ │
│ │ │Correct │ │ Wrong │ │
│ │ └───┬────┘ └───┬────┘ │
│ │ └─────┬─────┘ │
│ │ ┌────▼────┐ │
│ │ │ Step 5 │ │
│ │ │Conclude │ │
│ │ └─────────┘ │
├────────────┴─────────────────────────────────────────────────┤
│ Properties Panel │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Step 1: The Hook │ │
│ │ Duration: 25 seconds │ │
│ │ Actions: 2 blocks │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
## Step 3: Understanding Block Categories
### Events (Triggers)
Start your experiment with these blocks:
| Block | Description |
|-------|-------------|
| **Trial Start** | Triggers when trial begins |
| **Wizard Button** | Waits for wizard to press a button |
| **Timer** | Waits for a specified duration |
| **Participant Response** | Waits for participant input |
### Wizard Actions
Blocks the wizard can control:
| Block | Description |
|-------|-------------|
| **Say Text** | Robot speaks text |
| **Play Animation** | Play a predefined animation |
| **Show Image** | Display image on robot screen |
| **Move Robot** | Move robot to position |
### Control Flow
Control experiment progression:
| Block | Description |
|-------|-------------|
| **Branch** | Split into multiple paths |
| **Loop** | Repeat a sequence |
| **Wait** | Pause for duration |
| **Converge** | Merge multiple paths back |
### Robot Actions
Actions from your installed robot plugin:
| Block | Description |
|-------|-------------|
| **say_text** | Robot speaks |
| **walk_forward** | Robot walks forward |
| **turn_left** | Robot turns |
| **wave** | Robot waves |
## Step 4: Building "The Interactive Storyteller"
Let's build a simple storytelling experiment with branching:
### Step 1: The Hook (Start)
1. Click **+ Add Step**
2. Name it "The Hook"
3. Set type to **Robot**
4. Drag **Say Text** block:
```
text: "Hello! I have a story to tell you. Are you ready?"
```
5. Drag **Move Arm** block:
```
arm: right
gesture: welcome
```
### Step 2: The Narrative
1. Add new step "The Narrative"
2. Connect from Step 1
3. Add **Say Text**:
```
text: "Once upon a time, a traveler flew to Mars..."
```
4. Add **Turn Head** for gaze behavior:
```
yaw: 1.5
pitch: 0.0
```
### Step 3: Comprehension Check (Branching)
1. Add new step "Comprehension Check"
2. Set type to **Conditional**
3. Add **Ask Question**:
```
question: "What color was the rock?"
options:
- Correct: "Red"
- Incorrect: "Blue"
```
4. This creates two paths automatically
### Step 4: Branch Paths
**Branch A (Correct):**
```
Say: "Yes! It was a glowing red rock."
Emotion: Happy
```
**Branch B (Incorrect):**
```
Say: "Actually, it was red."
Emotion: Sad
```
### Step 5: Converge
1. Add new step "Story Continues"
2. Set type to **Converge**
3. Connect both branches to this step
4. Add concluding speech
### Step 6: Conclusion
1. Add final step "Conclusion"
2. Add **Say Text**: "The End. Thank you for listening!"
3. Add **Bow** animation
## Step 5: Block Properties
Each block has configurable properties:
### Say Text Block
```json
{
"text": "Hello, how are you?",
"language": "en-US",
"speed": 1.0,
"emotion": "neutral"
}
```
### Branch Block
```json
{
"variable": "last_response",
"options": [
{ "label": "Yes", "value": "yes", "nextStepId": "step_abc" },
{ "label": "No", "value": "no", "nextStepId": "step_xyz" }
]
}
```
### Loop Block
```json
{
"iterations": 3,
"maxDuration": 60,
"children": [...]
}
```
## Step 6: Testing Your Experiment
### Preview Mode
Test your experiment without running a real trial:
1. Click **Preview** button
2. Step through each block
3. See timing and flow
4. Test branching decisions
### Simulation Mode
Run with a simulated robot:
1. Enable `NEXT_PUBLIC_SIMULATION_MODE=true`
2. Start a trial
3. Robot actions are logged but not executed
4. Great for protocol testing
## Advanced: Parallel Execution
Run multiple actions simultaneously:
```
Step: Greeting
├── Parallel Block
│ ├── Say: "Hello!"
│ ├── Move Arm: Wave
│ └── Move Head: Look at participant
```
## Experiment Versioning
Track protocol changes:
1. **Draft** - Experiment being designed
2. **Testing** - Being tested with participants
3. **Ready** - Approved for data collection
4. **Deprecated** - Superseded by newer version
## Common Patterns
### Linear Protocol
```
Start → Step 1 → Step 2 → Step 3 → End
```
### Branching Protocol
```
Start → Step 1
├── Condition A → Step 2a
└── Condition B → Step 2b
```
### Loop Protocol
```
Start → Step 1 → Loop (3x) → Step 2 → End
└── (back to Step 1)
```
### Parallel Protocol
```
Start → Parallel
├── Action A
├── Action B
└── Action C
→ Continue
```
## Troubleshooting
### Block Not Connecting
- Check step types are compatible
- Ensure no circular dependencies
- Verify conditions are complete
### Robot Action Not Available
- Install the robot plugin
- Check plugin is enabled for study
- Verify robot is connected
### Timing Issues
- Adjust duration estimates
- Use explicit wait blocks
- Test with real timing
## Next Steps
Now that you've designed your experiment:
1. **[Running Trials](04-running-trials.md)** - Execute your experiment
2. **[Wizard Interface](05-wizard-interface.md)** - Learn real-time control
3. **[Robot Integration](06-robot-integration.md)** - Connect your robot
---
**Previous**: [Your First Study](02-your-first-study.md) | **Next**: [Running Trials](04-running-trials.md)
+366
View File
@@ -0,0 +1,366 @@
# Tutorial 4: Running Trials
Learn how to execute experiments and manage participant trials.
## Objectives
- Schedule and start trials
- Monitor trial progress
- Handle trial interruptions
- Collect trial data
## What is a Trial?
A **Trial** is a single execution of an experiment with one participant:
```
Trial
├── Participant (who took part)
├── Experiment (which protocol)
├── Status (scheduled, in_progress, completed)
├── Events (timestamped actions)
└── Data (collected responses)
```
## Trial Lifecycle
```
Scheduled → In Progress → Completed
│ │ │
│ ▼ │
│ Aborted ◄────────┤
│ │ │
└────────► Failed ◄───────┘
```
| Status | Description |
|--------|-------------|
| Scheduled | Trial is planned but not started |
| In Progress | Trial is currently running |
| Completed | Trial finished successfully |
| Aborted | Trial stopped early by wizard |
| Failed | Trial failed due to error |
## Step 1: Schedule a Trial
### Create Trial for Participant
1. Go to your **Study**
2. Open **Trials** tab
3. Click **Schedule Trial**
4. Select:
- **Participant**: P001
- **Experiment**: The Interactive Storyteller
- **Scheduled Time**: Today, 2:00 PM
### Batch Scheduling
For multiple participants:
1. Click **Batch Schedule**
2. Select participants (P001-P020)
3. Select experiment
4. Set time slots
```
| Time | Participant |
|------------|-------------|
| 2:00 PM | P001 |
| 2:15 PM | P002 |
| 2:30 PM | P003 |
| ... | ... |
```
## Step 2: Prepare for Trial
Before starting:
1. **Verify Robot Connection**
- Check robot is powered on
- Verify network connection
- Test WebSocket connection
2. **Review Experiment**
- Ensure experiment is "Ready" status
- Check step count and timing
- Verify all actions are configured
3. **Prepare Environment**
- Ensure participant consent is obtained
- Set up recording equipment (if needed)
- Remove distractions
## Step 3: Start a Trial
### From Trials List
1. Find the scheduled trial
2. Click **Start Trial**
3. Confirm participant is ready
4. Click **Begin**
### From Wizard Interface
1. Open **Wizard Interface**
2. Select trial from queue
3. Click **Start**
## Step 4: During the Trial
### Wizard Interface Overview
```
┌──────────────────────────────────────────────────────────────┐
│ Trial: P001 - Interactive Storyteller [00:05:23]│
├──────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌────────────────────┐ ┌─────────────────┐ │
│ │ Trial │ │ Timeline │ │ Robot Control │ │
│ │ Controls │ │ │ │ │ │
│ │ │ │ ●───●───○───○ │ │ ┌─────────────┐ │ │
│ │ [▶ Play] │ │ Step 1 2 3 4 │ │ │ Connected ✓ │ │ │
│ │ [⏸ Pause] │ │ ↑ │ │ │ Battery: 85%│ │ │
│ │ [⏹ Stop] │ │ Current: Step 2 │ │ └─────────────┘ │ │
│ │ │ │ │ │ │ │
│ │ [📝 Notes] │ │ Progress: 40% │ │ [Say Text] │ │
│ │ [⚠ Alert] │ │ │ │ [Move Robot] │ │
│ └──────────────┘ └────────────────────┘ │ [Custom Action]│ │
│ └─────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
### Trial Controls
| Button | Action | Keyboard |
|--------|--------|----------|
| Play | Resume trial | Space |
| Pause | Pause trial | Space |
| Stop | End trial early | Escape |
| Notes | Add timestamped note | N |
| Alert | Send alert notification | A |
### Monitoring Progress
**Timeline View:**
- Visual step progression
- Current step highlighted
- Completed steps checked
- Estimated time remaining
**Event Log:**
- Timestamped events
- Action executions
- Wizard interventions
- Robot responses
## Step 5: Wizard Interventions
During Wizard-of-Oz studies, wizards can intervene:
### Add Intervention
1. Click **+ Intervention**
2. Select type:
- **Pause**: Temporarily stop trial
- **Resume**: Continue after pause
- **Note**: Add observation
- **Alert**: Notify researcher
### Branch Selection
When reaching a conditional step:
1. Observe participant response
2. Select appropriate branch:
- **Correct**: Proceed to positive path
- **Incorrect**: Proceed to correction path
3. Select is logged for analysis
### Manual Actions
Execute unplanned actions:
1. Click **+ Action**
2. Select from robot actions
3. Configure parameters
4. Execute immediately
## Step 6: Trial Completion
### Automatic Completion
When all steps complete:
1. Final step executes
2. Trial status → "Completed"
3. Data is saved automatically
4. Summary shown
### Manual Completion
To end early:
1. Click **Stop Trial**
2. Confirm completion
3. Select reason:
- Participant fatigue
- Technical issue
- Protocol complete
4. Save partial data
## Step 7: Post-Trial
### Automatic Prompts
After trial completion:
1. **Participant Debrief**
- Thank participant
- Answer questions
- Collect final feedback
2. **Survey Distribution**
- Send post-session survey
- Collect responses
3. **Data Export**
- Download trial data
- Export event log
### Trial Summary
View trial summary:
```
┌─────────────────────────────────────────────────────────────┐
│ Trial Summary - P001 │
├─────────────────────────────────────────────────────────────┤
│ Duration: 5:23 │
│ Steps Completed: 6/6 (100%) │
│ Interventions: 2 │
│ │
│ Actions: │
│ ✓ Say Text: "Hello..." (2.3s) │
│ ✓ Turn Head: yaw=1.5 (1.1s) │
│ ✓ Say Text: "What color..." (3.2s) │
│ ⚠ Intervention: Pause (10s) │
│ ✓ Branch: Correct selected │
│ ✓ Say Text: "Yes! It was red" (2.8s) │
│ │
│ Events: 18 logged │
└─────────────────────────────────────────────────────────────┘
```
## Managing Multiple Trials
### Trial Queue
View upcoming trials:
```
┌─────────────────────────────────────────────────────────────┐
│ Trial Queue [Refresh] │
├─────────────────────────────────────────────────────────────┤
│ 2:00 PM │ P001 │ Interactive Storyteller │ Scheduled │
│ 2:20 PM │ P002 │ Interactive Storyteller │ Scheduled │
│ 2:40 PM │ P003 │ Interactive Storyteller │ Scheduled │
│ 3:00 PM │ P004 │ Interactive Storyteller │ Scheduled │
└─────────────────────────────────────────────────────────────┘
```
### Trial History
View past trials:
| Participant | Started | Duration | Status | Interventions |
|-------------|---------|----------|--------|---------------|
| P001 | Today 2:00 PM | 5:23 | Completed | 2 |
| P002 | Today 2:20 PM | 4:58 | Completed | 1 |
| P003 | Today 2:45 PM | - | In Progress | 0 |
## Data Collection
### Automatic Data Capture
HRIStudio automatically logs:
- Timestamps for all events
- Action executions
- Robot responses
- Wizard interventions
- Participant responses
- Timing data
### Manual Data
Wizards can add:
- Timestamped notes
- Observation categories
- Participant behavior codes
- Custom annotations
### Export Formats
Download trial data:
| Format | Contents |
|--------|----------|
| CSV | Tabular data for spreadsheets |
| JSON | Full event log with metadata |
| Video | Screen recording (if enabled) |
## Troubleshooting
### Trial Won't Start
1. Check robot connection
2. Verify experiment is "Ready"
3. Check participant consent
4. Review error logs
### Trial Paused Unexpectedly
- Robot may have disconnected
- Check network connection
- Resume when connection restored
### Data Not Saved
- Ensure database connection
- Check disk space
- Export data manually
## Best Practices
### Before Trials
- [ ] Robot connected and tested
- [ ] Experiment verified
- [ ] Participant consent obtained
- [ ] Recording equipment ready
- [ ] Wizard briefed on protocol
### During Trials
- [ ] Monitor timeline progress
- [ ] Take timestamped notes
- [ ] Document interventions
- [ ] Watch for issues
### After Trials
- [ ] Review trial summary
- [ ] Export data promptly
- [ ] Send follow-up surveys
- [ ] Update participant status
## Next Steps
Now that you can run trials:
1. **[Wizard Interface](05-wizard-interface.md)** - Master real-time control
2. **[Data & Analysis](08-data-and-analysis.md)** - Analyze your results
3. **[Forms & Surveys](07-forms-and-surveys.md)** - Collect post-trial data
---
**Previous**: [Designing Experiments](03-designing-experiments.md) | **Next**: [Wizard Interface](05-wizard-interface.md)
+393
View File
@@ -0,0 +1,393 @@
# Tutorial 5: Wizard Interface
Learn how to use the real-time wizard control interface for Wizard-of-Oz studies.
## Objectives
- Navigate the wizard interface
- Control robot actions in real-time
- Make branching decisions
- Handle trial interruptions
## What is the Wizard Interface?
The **Wizard Interface** is your control center during trials. It provides:
- Real-time trial monitoring
- Robot action controls
- Decision-making tools
- Intervention capabilities
- Event logging
```
┌──────────────────────────────────────────────────────────────┐
│ WIZARD INTERFACE │
├────────────────┬─────────────────────┬──────────────────────┤
│ │ │ │
│ Trial │ Timeline │ Robot │
│ Controls │ Progress │ Status │
│ │ │ │
│ ┌──────────┐ │ ┌───────────────┐ │ ┌────────────────┐ │
│ │ ▶ Play │ │ │ 1 → 2 → 3 → │ │ │ ● Connected │ │
│ │ ⏸ Pause │ │ │ ↑ │ │ │ Battery: 85% │ │
│ │ ⏹ Stop │ │ │ Step 2 │ │ │ Position: (0,0)│ │
│ └──────────┘ │ └───────────────┘ │ └────────────────┘ │
│ │ │ │
│ ┌──────────┐ │ Progress: 40% │ ┌────────────────┐ │
│ │ 📝 Notes │ │ Time: 00:05:23 │ │ Action Panel │ │
│ │ ⚠ Alert │ │ │ │ │ │
│ └──────────┘ │ │ │ [Say Text] │ │
│ │ │ │ [Move Robot] │ │
│ │ │ │ [Wave] │ │
│ │ │ │ [Custom...] │ │
│ │ │ └────────────────┘ │
└────────────────┴─────────────────────┴──────────────────────┘
```
## Step 1: Accessing the Wizard Interface
### Method 1: From Trials List
1. Go to **Trials** in sidebar
2. Find your scheduled trial
3. Click **Open Wizard**
### Method 2: Direct URL
```
/trials/{trialId}/wizard
```
### Method 3: Trial Queue
1. Go to **Wizard Queue**
2. See all pending trials
3. Click **Start** on any trial
## Step 2: Understanding the Layout
### Left Panel: Trial Controls
| Control | Function |
|---------|----------|
| Play/Pause | Start or pause trial |
| Stop | End trial early |
| Notes | Add timestamped observations |
| Alert | Send alert to researchers |
### Center Panel: Timeline
- **Visual Progress**: See step progression
- **Current Position**: Highlighted current step
- **Navigation**: Click to jump to step (if allowed)
- **Time Display**: Elapsed and estimated remaining
### Right Panel: Robot Control
**Status Section:**
- Connection indicator
- Battery level
- Position tracking
- Sensor readings
**Action Section:**
- Quick action buttons
- Custom action builder
- Action history
## Step 3: Controlling the Robot
### Quick Actions
Pre-configured robot actions:
| Action | Description |
|--------|-------------|
| Say Text | Make robot speak |
| Wave | Wave gesture |
| Look at Me | Turn head toward participant |
| Look Away | Turn head elsewhere |
| Nod | Confirmation nod |
| Shake Head | Negation shake |
### Custom Say Text
1. Click **Say Text**
2. Enter text in popup:
```
"Hello! Nice to meet you."
```
3. Select options:
- Speed: Normal / Slow / Fast
- Emotion: Neutral / Happy / Excited
4. Click **Execute**
5. Robot speaks the text
### Move Robot
1. Click **Move Robot**
2. Select movement type:
- Walk Forward/Back
- Turn Left/Right
- Move Head
- Move Arm
3. Set parameters
4. Execute
### Custom Actions
For advanced control:
1. Click **Custom...**
2. Select action from plugin
3. Configure parameters
4. Execute
## Step 4: Making Decisions
When the experiment reaches a branching point:
### Decision Popup
A popup appears with options:
```
┌─────────────────────────────────────────────────────────────┐
│ Branch Decision Required │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step: Comprehension Check │
│ Question: "What color was the rock?" │
│ │
│ Participant's response: They said "blue" (incorrect) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ○ Correct Response (Red) │ │
│ │ → Robot celebrates │ │
│ │ │ │
│ │ ● Incorrect Response (Other) │ │
│ │ → Robot gently corrects │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Confirm Selection] │
└─────────────────────────────────────────────────────────────┘
```
### Decision Guidelines
1. **Observe** participant's actual response
2. **Consider** protocol criteria
3. **Select** appropriate branch
4. **Confirm** selection
### After Selection
- Decision is logged with timestamp
- Trial continues on selected path
- Both participant and robot continue
## Step 5: Handling Interruptions
### Pause Trial
When you need to pause:
1. Click **Pause** button
2. Optionally add reason:
- Participant needs break
- Technical issue
- External interruption
3. Trial pauses, robot holds position
### Resume Trial
1. Click **Play** button
2. Trial resumes from pause point
3. Pause duration is logged
### Stop Trial
For early termination:
1. Click **Stop** button
2. Select reason:
- Participant fatigue
- Technical failure
- Protocol deviation
- Participant withdrawal
3. Confirm stop
4. Partial data is saved
### Add Notes
Record observations:
1. Click **Notes** button
2. Enter observation:
```
Participant laughed at the robot's gesture.
```
3. Note is timestamped automatically
4. Notes appear in event log
### Send Alert
Notify researchers:
1. Click **Alert** button
2. Select alert type:
- Technical issue
- Safety concern
- Protocol question
- Other
3. Add description
4. Send alert
## Step 6: Monitoring Robot Status
### Connection Status
| Status | Icon | Meaning |
|--------|------|---------|
| Connected | ● Green | Robot responding |
| Connecting | ● Yellow | Attempting connection |
| Disconnected | ● Red | No robot connection |
| Error | ⚠ Orange | Connection error |
### Battery Monitor
View battery level:
- Green: > 50%
- Yellow: 20-50%
- Red: < 20%
### Sensor Display
Real-time sensor readings:
- Joint positions
- Touch sensors
- Sonar distances
- Camera feed (if available)
### Action Queue
See pending/executing actions:
```
Executing: Say Text "Hello!"
Pending: Move Head (queued)
```
## Step 7: Keyboard Shortcuts
Speed up your workflow:
| Key | Action |
|-----|--------|
| Space | Play/Pause toggle |
| Escape | Stop trial |
| N | Add note |
| A | Send alert |
| 1-9 | Execute quick action |
| ← → | Navigate timeline |
| ↑ ↓ | Select branch option |
## Step 8: Event Logging
All actions are logged automatically:
```
[14:32:05] Trial started
[14:32:07] Step 1: The Hook
[14:32:08] Action: Say Text "Hello!"
[14:32:11] Action: Move Arm Wave
[14:32:15] Step 2: The Narrative
[14:32:16] Action: Say Text "Once upon a time..."
[14:33:05] Step 3: Comprehension Check
[14:33:06] Action: Say Text "What color was the rock?"
[14:33:28] Wizard Note: "Participant said blue"
[14:33:30] Branch: Incorrect selected
[14:33:31] Step 4b: Correction
[14:33:32] Action: Say Text "Actually, it was red."
[14:34:05] Trial completed
```
## Trial Modes
### Observer Mode
For observers (read-only):
- View trial progress
- See robot status
- Cannot execute actions
- Can add notes
### Active Wizard Mode
Full control:
- Execute actions
- Make decisions
- Pause/resume
- Add notes/alerts
### Training Mode
Practice without real data:
- Simulated robot
- No data saved
- Safe to experiment
## Best Practices
### Before Trial
- [ ] Review experiment protocol
- [ ] Test robot connection
- [ ] Familiarize with action panel
- [ ] Know decision criteria
### During Trial
- [ ] Stay focused on participant
- [ ] Make decisions based on observation
- [ ] Document notable events
- [ ] Keep action log clean
### After Trial
- [ ] Review event log
- [ ] Add final notes
- [ ] Confirm data saved
- [ ] Prepare for next trial
## Troubleshooting
### Robot Not Responding
1. Check connection indicator
2. Verify network
3. Check robot power
4. Restart connection
### Actions Not Executing
1. Check action queue
2. Verify parameters
3. Check robot state (not in rest mode)
### Decision Popup Not Appearing
1. Check if step has branches
2. Verify step type is "conditional"
3. Contact researcher
## Next Steps
Mastered the wizard interface?
1. **[Robot Integration](06-robot-integration.md)** - Deep dive into robot control
2. **[Data & Analysis](08-data-and-analysis.md)** - Review trial data
3. **[Simulation Mode](09-simulation-mode.md)** - Practice without a robot
---
**Previous**: [Running Trials](04-running-trials.md) | **Next**: [Robot Integration](06-robot-integration.md)
+386
View File
@@ -0,0 +1,386 @@
# Tutorial 6: Robot Integration
Learn how to connect and configure robots for your HRI studies.
## Objectives
- Connect NAO6 robot to HRIStudio
- Configure robot plugins
- Test robot connection
- Troubleshoot common issues
## Supported Robots
HRIStudio supports multiple robot platforms:
| Robot | Protocol | Actions |
|-------|----------|---------|
| **NAO6** | ROS2 | Speech, movement, gestures, sensors |
| **TurtleBot3** | ROS2 | Navigation, sensors |
| **Mock Robot** | WebSocket | All actions (simulation) |
## Understanding the Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ HRIStudio Platform │
│ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Wizard │◄────────────►│ Robot Communication │ │
│ │ Interface │ WebSocket │ Service │ │
│ └──────────────┘ └──────────┬───────────┘ │
│ │ │
│ │ ROS Bridge │
│ ┌─────▼─────┐ │
│ │ rosbridge │ │
│ │ :9090 │ │
│ └─────┬─────┘ │
└────────────────────────────────────────────┼─────────────────┘
┌─────────────────┼─────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ │
│ NAO │ │ NAO │ │
│ Driver │ │ Robot │ │
│ (ROS2) │◄───►│ (naoqi) │ │
└───────────┘ └───────────┘ │
Network Robot │
```
## Step 1: Set Up NAO6 Robot
### Network Configuration
1. Connect NAO6 to your network:
```
# On the robot, say "Connect to Wi-Fi"
# Or use the Choregraphe interface
```
2. Note the robot's IP address:
```
# On the robot, say "What is my IP address?"
# Or check robot's network settings
```
3. Verify network access:
```bash
ping nao.local
# Or ping the IP directly:
ping 192.168.1.100
```
### Robot Credentials
Default credentials:
```
Username: nao
Password: robolab
```
### Wake Up Robot
Before connecting, wake up the robot:
```bash
ssh nao@192.168.1.100
# Enter password when prompted
# Wake up the robot
python -c "from naoqi import ALProxy; proxy = ALProxy('ALMotion', '192.168.1.100', 9559); proxy.wakeUp()"
```
## Step 2: Start Docker Services
### Using Docker Compose
```bash
cd ~/nao6-hristudio-integration
# Set robot IP
export NAO_IP=192.168.1.100
# Start services
docker compose up -d
```
### Services Overview
| Service | Port | Purpose |
|---------|------|---------|
| `nao_driver` | - | ROS2 driver for NAO |
| `ros_bridge` | 9090 | WebSocket bridge |
| `ros_api` | - | Topic introspection |
### Verify Services
```bash
# Check running containers
docker ps
# View logs
docker compose logs -f
# Test WebSocket connection
ws://localhost:9090
```
## Step 3: Configure HRIStudio
### Install Robot Plugin
1. Go to **Plugins** in sidebar
2. Select your study
3. Click **Browse Plugins**
4. Find **NAO6 Robot (ROS2 Integration)**
5. Click **Install**
### Configure Plugin
Set robot connection:
```
┌─────────────────────────────────────────────────────────────┐
│ NAO6 Robot Configuration │
├─────────────────────────────────────────────────────────────┤
│ Robot Name: NAO6-Lab │
│ Robot IP: 192.168.1.100 │
│ WebSocket URL: ws://localhost:9090 │
│ │
│ Advanced Settings: │
│ □ Use Simulation Mode │
│ Connection Timeout: 30 seconds │
└─────────────────────────────────────────────────────────────┘
```
### Environment Variables
Create `hristudio/.env.local`:
```bash
# Robot connection
NAO_ROBOT_IP=192.168.1.100
NAO_PASSWORD=robolab
NAO_USERNAME=nao
# WebSocket bridge
NEXT_PUBLIC_ROS_BRIDGE_URL=ws://localhost:9090
```
## Step 4: Test Connection
### Using the NAO Test Page
1. Navigate to: `http://localhost:3000/nao-test`
2. Click **Connect**
3. Verify connection status
### Connection Status Indicators
| Status | Meaning |
|--------|---------|
| **Connected** | Robot responding normally |
| **Connecting** | Attempting connection |
| **Error** | Connection failed |
| **Timeout** | Robot not responding |
### Test Actions
Test basic robot actions:
| Action | Expected Behavior |
|--------|-------------------|
| Say Text | Robot speaks |
| Wave | Robot waves arm |
| Walk Forward | Robot walks |
| Turn Left | Robot turns |
## Step 5: Robot Actions Reference
### Speech Actions
| Action | Parameters | Description |
|--------|------------|-------------|
| `say_text` | `text` | Speak text |
| `say_with_emotion` | `text`, `emotion` | Emotional speech |
| `set_volume` | `level` | Set speech volume |
| `set_language` | `language` | Set speech language |
### Movement Actions
| Action | Parameters | Description |
|--------|------------|-------------|
| `walk_forward` | `speed`, `duration` | Walk forward |
| `walk_backward` | `speed` | Walk backward |
| `turn_left` | `speed` | Turn left |
| `turn_right` | `speed` | Turn right |
| `stop` | - | Stop all movement |
### Head Actions
| Action | Parameters | Description |
|--------|------------|-------------|
| `move_head` | `yaw`, `pitch`, `speed` | Move head to position |
| `turn_head` | `yaw`, `pitch` | Turn head (relative) |
### Arm Actions
| Action | Parameters | Description |
|--------|------------|-------------|
| `move_arm` | `arm`, joint angles | Move arm to position |
| `wave` | `arm` | Wave gesture |
### Autonomous Life
| Action | Parameters | Description |
|--------|------------|-------------|
| `wake_up` | - | Wake robot from rest |
| `rest` | - | Put robot to rest |
| `set_autonomous_life` | `enabled` | Toggle autonomous behavior |
## Step 6: Troubleshooting
### Common Issues
#### Robot Not Found
```
Error: Cannot connect to robot at 192.168.1.100
```
**Solutions:**
1. Verify IP address: `ping 192.168.1.100`
2. Check robot is powered on
3. Verify network connectivity
4. Try `nao.local` hostname
#### WebSocket Connection Failed
```
Error: WebSocket connection to ws://localhost:9090 failed
```
**Solutions:**
1. Check Docker is running
2. Verify ros_bridge container: `docker ps`
3. Check port 9090 is not blocked
4. Restart services: `docker compose restart`
#### Robot Not Responding
Robot connected but actions don't execute.
**Solutions:**
1. Wake up robot: `ssh nao@IP python -c "from naoqi import ALProxy; p=ALProxy('ALMotion','IP',9559);p.wakeUp()"`
2. Check robot is not in rest mode
3. Verify no blocking software on robot
#### Action Timeout
```
Error: Action timed out after 30 seconds
```
**Solutions:**
1. Robot may be busy with previous action
2. Check network latency
3. Increase timeout in settings
### Diagnostic Commands
```bash
# Check Docker containers
docker ps
# View all logs
docker compose logs
# View specific service
docker compose logs ros_bridge
# Restart services
docker compose restart
# Stop and start fresh
docker compose down
docker compose up -d
```
### Network Troubleshooting
```bash
# Check robot IP
ssh nao@IP "ifconfig"
# Test from robot
ssh nao@IP "curl localhost:9090"
# Check firewall
sudo iptables -L
```
## Step 7: Robot Maintenance
### Battery Management
- Check battery before each session
- Aim for >50% battery
- Charge during breaks
- Replace battery if <20% capacity
### Calibration
Periodically calibrate:
- Joint positions
- Camera alignment
- Touch sensors
- Sound localization
### Software Updates
Keep robot software updated:
- NAOqi version
- ROS2 packages
- HRIStudio plugin
## Security Considerations
### Network Security
- Use encrypted network (WPA2/WPA3)
- Firewall robot from internet
- Use strong passwords
### SSH Access
- Change default passwords
- Use SSH keys when possible
- Limit SSH access
### Data Security
- Robot camera data may be sensitive
- Store data securely
- Follow IRB guidelines
## Simulation Mode
For testing without a robot:
1. Enable simulation mode in settings
2. Or set `NEXT_PUBLIC_SIMULATION_MODE=true`
3. All actions are simulated locally
See [Simulation Mode Tutorial](09-simulation-mode.md) for details.
## Next Steps
Now that your robot is connected:
1. **[Running Trials](04-running-trials.md)** - Execute trials with robot
2. **[Wizard Interface](05-wizard-interface.md)** - Control the robot
3. **[Data & Analysis](08-data-and-analysis.md)** - Collect interaction data
---
**Previous**: [Wizard Interface](05-wizard-interface.md) | **Next**: [Forms & Surveys](07-forms-and-surveys.md)
+505
View File
@@ -0,0 +1,505 @@
# Tutorial 7: Forms & Surveys
Learn how to create and manage consent forms, surveys, and questionnaires.
## Objectives
- Create consent forms for IRB compliance
- Build post-session surveys
- Collect participant responses
- Manage form templates
## Form Types
HRIStudio supports three form types:
| Type | Purpose | When |
|------|---------|------|
| **Consent** | Informed consent for participation | Before trial |
| **Survey** | Collect feedback and observations | After trial |
| **Questionnaire** | Demographic data collection | Any time |
## Step 1: Access Forms
1. Go to your **Study**
2. Click **Forms** tab
3. View existing forms and templates
### Form List View
```
┌─────────────────────────────────────────────────────────────┐
│ Forms [+ Create] │
├─────────────────────────────────────────────────────────────┤
│ Name Type Responses Status │
│ ─────────────────────────────────────────────────────────── │
│ Informed Consent Consent 12/20 Active │
│ Post-Session Survey Survey 8/20 Active │
│ Demographics Questionnaire 15/20 Active │
│ Template: Standard Consent - Template │
│ Template: Feedback Survey - Template │
└─────────────────────────────────────────────────────────────┘
```
## Step 2: Create a Form
### Using a Template
1. Click **Create Form**
2. Select **Use Template**
3. Choose template:
- Informed Consent
- Post-Session Survey
- Demographics
4. Customize as needed
### From Scratch
1. Click **Create Form**
2. Select **Blank Form**
3. Choose form type
4. Build fields manually
## Step 3: Form Builder
The form builder lets you create custom fields:
```
┌─────────────────────────────────────────────────────────────┐
│ Form Builder: Post-Session Survey │
├─────────────────────────────────────────────────────────────┤
│ │
│ Form Settings │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Title: Post-Session Survey │ │
│ │ Type: Survey │ │
│ │ Active: ☑ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Fields │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. [Rating] How engaging was the robot? [✕] │ │
│ │ 2. [Text] What did you enjoy most? [✕] │ │
│ │ 3. [Multiple Choice] Robot personality? [✕] │ │
│ │ │ │
│ │ [+ Add Field] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Preview │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ How engaging was the robot? │ │
│ │ ○ 1 ○ 2 ○ 3 ○ 4 ○ 5 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Save] │
└─────────────────────────────────────────────────────────────┘
```
## Step 4: Field Types
### Text Field
```
┌─────────────────────────────────────────────────────────────┐
│ Field Type: Text │
├─────────────────────────────────────────────────────────────┤
│ Label: Participant Age │
│ Required: ☑ │
│ Placeholder: e.g., 25 │
│ │
│ Preview: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Participant Age * │ │
│ │ [e.g., 25 ] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Rating Scale
```
┌─────────────────────────────────────────────────────────────┐
│ Field Type: Rating │
├─────────────────────────────────────────────────────────────┤
│ Label: How engaging was the robot? │
│ Required: ☑ │
│ Scale: 1 to [5] │
│ Low Label: Not at all engaging │
│ High Label: Very engaging │
│ │
│ Preview: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ How engaging was the robot? * │ │
│ │ │ │
│ │ 1 2 3 4 5 │ │
│ │ ○ ○ ○ ○ ○ │ │
│ │ Not at all Very engaging │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Multiple Choice
```
┌─────────────────────────────────────────────────────────────┐
│ Field Type: Multiple Choice │
├─────────────────────────────────────────────────────────────┤
│ Label: Did the robot respond appropriately? │
│ Required: ☑ │
│ Options: │
│ 1. Yes, always │
│ 2. Yes, most of the time │
│ 3. Sometimes │
│ 4. Rarely │
│ 5. No │
│ │
│ Preview: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Did the robot respond appropriately? * │ │
│ │ │ │
│ │ ○ Yes, always │ │
│ │ ○ Yes, most of the time │ │
│ │ ○ Sometimes │ │
│ │ ○ Rarely │ │
│ │ ○ No │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Yes/No
```
┌─────────────────────────────────────────────────────────────┐
│ Field Type: Yes/No │
├─────────────────────────────────────────────────────────────┤
│ Label: Would you interact with this robot again? │
│ Required: ☐ │
│ │
│ Preview: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Would you interact with this robot again? │ │
│ │ │ │
│ │ ○ Yes ○ No │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Text Area
```
┌─────────────────────────────────────────────────────────────┐
│ Field Type: Text Area │
├─────────────────────────────────────────────────────────────┤
│ Label: What did you enjoy most about the interaction? │
│ Required: ☐ │
│ Rows: [4] │
│ │
│ Preview: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ What did you enjoy most about the interaction? │ │
│ │ │ │
│ │ [ ] │ │
│ │ [ ] │ │
│ │ [ ] │ │
│ │ [ ] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Date
```
┌─────────────────────────────────────────────────────────────┐
│ Field Type: Date │
├─────────────────────────────────────────────────────────────┤
│ Label: Session Date │
│ Required: ☑ │
│ │
│ Preview: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Session Date * │ │
│ │ [📅 Select date ] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Signature
```
┌─────────────────────────────────────────────────────────────┐
│ Field Type: Signature │
├─────────────────────────────────────────────────────────────┤
│ Label: Participant Signature │
│ Required: ☑ │
│ │
│ Preview: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Participant Signature * │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ [Sign here] │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Step 5: Consent Forms
### Required Elements
For IRB compliance, consent forms must include:
- [ ] Study title and purpose
- [ ] Principal investigator
- [ ] Procedures description
- [ ] Risks and benefits
- [ ] Confidentiality statement
- [ ] Voluntary participation note
- [ ] Signature and date fields
### Consent Form Template
```json
{
"title": "Informed Consent",
"type": "consent",
"fields": [
{ "type": "text", "label": "Study Title", "required": true },
{ "type": "text", "label": "Principal Investigator", "required": true },
{ "type": "textarea", "label": "Purpose of the Study", "required": true },
{ "type": "textarea", "label": "Procedures", "required": true },
{ "type": "textarea", "label": "Risks and Benefits", "required": true },
{ "type": "textarea", "label": "Confidentiality", "required": true },
{ "type": "yes_no", "label": "I consent to participate", "required": true },
{ "type": "signature", "label": "Participant Signature", "required": true },
{ "type": "date", "label": "Date", "required": true }
]
}
```
## Step 6: Surveys
### Post-Session Survey Example
```json
{
"title": "Post-Session Questionnaire",
"type": "survey",
"fields": [
{
"type": "rating",
"label": "How engaging was the robot?",
"settings": { "scale": 5 }
},
{
"type": "rating",
"label": "How natural did the interaction feel?",
"settings": { "scale": 5 }
},
{
"type": "multiple_choice",
"label": "Did the robot respond appropriately?",
"options": ["Always", "Usually", "Sometimes", "Rarely", "Never"]
},
{
"type": "textarea",
"label": "What did you like most?"
},
{
"type": "textarea",
"label": "What could be improved?"
}
]
}
```
### Questionnaire Example (Demographics)
```json
{
"title": "Demographics",
"type": "questionnaire",
"fields": [
{ "type": "text", "label": "Age" },
{
"type": "multiple_choice",
"label": "Gender",
"options": ["Male", "Female", "Non-binary", "Prefer not to say"]
},
{
"type": "multiple_choice",
"label": "Experience with robots",
"options": ["None", "A little", "Moderate", "Extensive"]
}
]
}
```
## Step 7: Form Versions
Forms support versioning for IRB compliance:
1. Create new version when modifying:
- Question text changes
- New fields added
- Required fields changed
2. Version history:
```
Version 1 (Current) - Active
Version 2 - Draft
Version 3 - Archived
```
3. Track changes:
- Version number
- Change date
- Change description
## Step 8: Distributing Forms
### Automatic Distribution
Configure automatic form sending:
1. Open form settings
2. Enable **Auto-distribute**
3. Set trigger:
- Before trial (consent)
- After trial (survey)
4. Select participants
### Manual Distribution
Send forms manually:
1. Open form
2. Click **Distribute**
3. Select participants
4. Choose delivery method
### Participant Link
Generate shareable link:
```
https://hristudio.example.com/forms/{formId}?participant={participantCode}
```
## Step 9: Collecting Responses
### View Responses
1. Open form
2. Click **Responses** tab
3. View individual submissions
### Response Dashboard
```
┌─────────────────────────────────────────────────────────────┐
│ Form Responses: Post-Session Survey │
├─────────────────────────────────────────────────────────────┤
│ Total Responses: 15/20 (75%) │
│ │
│ Question: How engaging was the robot? │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 5 ████████████████████████████████████ 8 responses │ │
│ │ 4 ██████████████████ 5 responses │ │
│ │ 3 ████████ 2 responses │ │
│ │ 2 ████ 1 response │ │
│ │ 1 ████ 1 response │ │
│ │ │ │
│ │ Average: 4.2 / 5.0 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### Export Responses
Download collected data:
| Format | Contents |
|--------|----------|
| CSV | Tabular data |
| JSON | Full response objects |
| PDF | Printed consent forms |
## Step 10: Form Templates
### Creating Templates
1. Create form with desired fields
2. Click **Save as Template**
3. Enter template name
4. Template is available for reuse
### Template Library
| Template | Use Case |
|----------|----------|
| Standard Consent | Generic research consent |
| Child Consent | Studies with minors |
| Extended Consent | Complex procedures |
| Feedback Survey | Post-session feedback |
| NASA-TLX | Workload assessment |
| SUS | System usability |
## Best Practices
### Consent Forms
- [ ] Review with IRB before use
- [ ] Keep language simple
- [ ] Include all required elements
- [ ] Version control for changes
- [ ] Store signed forms securely
### Surveys
- [ ] Keep questions concise
- [ ] Use appropriate scales
- [ ] Test with pilot participants
- [ ] Randomize order when appropriate
- [ ] Include open-ended questions
### Data Management
- [ ] Export data regularly
- [ ] Backup responses
- [ ] Anonymize data for analysis
- [ ] Follow data retention policy
## Troubleshooting
### Form Not Loading
- Check form is active
- Verify participant access
- Check network connection
### Response Not Saving
- Check required fields
- Verify session active
- Try again or refresh
### Participant Can't Access
- Verify participant code valid
- Check form is distributed
- Confirm study is active
## Next Steps
Now that you've created your forms:
1. **[Running Trials](04-running-trials.md)** - Connect forms to trials
2. **[Data & Analysis](08-data-and-analysis.md)** - Analyze collected data
3. **[Your First Study](02-your-first-study.md)** - Set up your study
---
**Previous**: [Robot Integration](06-robot-integration.md) | **Next**: [Data & Analysis](08-data-and-analysis.md)
+505
View File
@@ -0,0 +1,505 @@
# Tutorial 8: Data & Analysis
Learn how to collect, export, and analyze trial data from HRIStudio.
## Objectives
- Understand data collection in HRIStudio
- Export trial data in various formats
- Analyze event logs
- Generate reports
## Data Collection Overview
HRIStudio automatically captures comprehensive data during trials:
```
┌─────────────────────────────────────────────────────────────┐
│ Data Collection │
├─────────────────────────────────────────────────────────────┤
│ │
│ Trial Metadata │
│ ├── Start/End times │
│ ├── Duration │
│ ├── Participant info │
│ └── Experiment version │
│ │
│ Event Log (Timestamped) │
│ ├── Step changes │
│ ├── Action executions │
│ ├── Robot responses │
│ └── Wizard interventions │
│ │
│ Form Responses │
│ ├── Consent forms │
│ ├── Surveys │
│ └── Questionnaires │
│ │
│ Sensor Data │
│ ├── Joint positions │
│ ├── Touch events │
│ └── Audio/video (if enabled) │
│ │
└─────────────────────────────────────────────────────────────┘
```
## Step 1: Accessing Trial Data
### From Trial List
1. Go to **Trials** tab
2. Find completed trial
3. Click **View Details**
### From Study Dashboard
1. Open your study
2. Go to **Data** tab
3. Select trial or view aggregate
## Step 2: Trial Event Log
Each trial generates a complete event log:
```json
{
"trialId": "trial_abc123",
"participantCode": "P001",
"experimentName": "Interactive Storyteller",
"startedAt": "2024-03-15T14:00:00Z",
"completedAt": "2024-03-15T14:05:23Z",
"duration": 323,
"status": "completed",
"events": [
{
"timestamp": "2024-03-15T14:00:00.123Z",
"type": "trial_started",
"stepId": null,
"data": {}
},
{
"timestamp": "2024-03-15T14:00:02.456Z",
"type": "step_changed",
"stepId": "step_1",
"stepName": "The Hook",
"data": {}
},
{
"timestamp": "2024-03-15T14:00:03.789Z",
"type": "action_executed",
"actionName": "Say Text",
"parameters": { "text": "Hello!" },
"duration": 2300,
"status": "completed"
},
{
"timestamp": "2024-03-15T14:00:08.012Z",
"type": "action_executed",
"actionName": "Wave",
"duration": 1500,
"status": "completed"
},
{
"timestamp": "2024-03-15T14:02:30.123Z",
"type": "intervention",
"interventionType": "note",
"data": { "note": "Participant laughed" }
},
{
"timestamp": "2024-03-15T14:03:00.456Z",
"type": "wizard_response",
"variable": "last_response",
"selectedValue": "correct",
"data": {}
},
{
"timestamp": "2024-03-15T14:05:23.789Z",
"type": "trial_completed",
"data": { "stepsCompleted": 6 }
}
]
}
```
### Event Types
| Event Type | Description | Data Captured |
|------------|-------------|---------------|
| `trial_started` | Trial began | Timestamp |
| `step_changed` | New step began | Step ID, name |
| `action_executed` | Robot action | Action details, duration |
| `action_completed` | Action finished | Duration, result |
| `action_failed` | Action failed | Error details |
| `wizard_response` | Wizard decision | Selected option |
| `intervention` | Wizard intervention | Type, note |
| `trial_paused` | Trial paused | Reason |
| `trial_resumed` | Trial resumed | Pause duration |
| `trial_completed` | Trial finished | Summary |
## Step 3: Exporting Data
### Export Single Trial
1. Open trial details
2. Click **Export**
3. Select format
### Export Study Data
1. Open study
2. Go to **Data** tab
3. Click **Export All**
4. Select options:
- Date range
- Trial status
- Include forms
### Export Formats
#### CSV Format
```csv
trial_id,participant,experiment,started_at,duration,status,steps_completed
trial_abc,P001,Interactive Storyteller,2024-03-15T14:00:00Z,323,completed,6
trial_def,P002,Interactive Storyteller,2024-03-15T14:20:00Z,298,completed,6
trial_ghi,P003,Interactive Storyteller,2024-03-15T14:40:00Z,0,failed,1
```
#### JSON Format
```json
{
"exportDate": "2024-03-15T15:00:00Z",
"studyName": "Robot Trust Study",
"trials": [...],
"forms": [...],
"metadata": {
"totalTrials": 20,
"completedTrials": 18,
"averageDuration": 312
}
}
```
#### Event Log CSV
```csv
timestamp,event_type,step_name,action_name,parameters,duration,status
2024-03-15T14:00:00.123Z,trial_started,,,,,
2024-03-15T14:00:02.456Z,step_changed,The Hook,,,,
2024-03-15T14:00:03.789Z,action_executed,The Hook,Say Text,"{""text"":""Hello!""}",2300,completed
2024-03-15T14:00:08.012Z,action_executed,The Hook,Wave,,1500,completed
2024-03-15T14:02:30.123Z,intervention,The Narrative,Note,"{""note"":""Participant laughed""}",,,
2024-03-15T14:03:00.456Z,wizard_response,Comprehension Check,Correct,,,,
2024-03-15T14:05:23.789Z,trial_completed,,,,323,
```
## Step 4: Data Dashboard
### Study Dashboard
View aggregate statistics:
```
┌─────────────────────────────────────────────────────────────┐
│ Study Dashboard: Robot Trust Study │
├─────────────────────────────────────────────────────────────┤
│ │
│ Overview │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 20 │ │ 18 │ │ 5m12s │ │ 2 │ │
│ │ Trials │ │ Complete│ │ Avg Time│ │ Failed │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Completion Rate │
│ ████████████████████████████████████░░░░ 90% │
│ │
│ Timeline │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ P001 ████████████████████████████████ 5:23 │ │
│ │ P002 ██████████████████████████████ 5:02 │ │
│ │ P003 ██████████████████████████ 4:45 │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Metrics
| Metric | Description |
|--------|-------------|
| Total Trials | Number of scheduled trials |
| Completed | Successfully completed trials |
| Average Duration | Mean trial time |
| Completion Rate | % of trials completed |
| Failed | Trials that failed |
| Average Steps | Mean steps per trial |
## Step 5: Analyzing Event Data
### Timing Analysis
Calculate action durations:
```python
import json
with open('trial_events.json') as f:
data = json.load(f)
# Calculate action durations
for event in data['events']:
if event['type'] == 'action_executed':
duration = event.get('duration', 0)
print(f"{event['actionName']}: {duration/1000:.1f}s")
```
### Intervention Analysis
Track wizard interventions:
```python
# Count interventions by type
interventions = [
e for e in data['events']
if e['type'] == 'intervention'
]
by_type = {}
for i in interventions:
itype = i['data'].get('type', 'unknown')
by_type[itype] = by_type.get(itype, 0) + 1
print(by_type)
# {'note': 15, 'pause': 3, 'alert': 1}
```
### Branch Selection Analysis
Analyze wizard decisions:
```python
# Get wizard responses
responses = [
e for e in data['events']
if e['type'] == 'wizard_response'
]
# Count by value
by_value = {}
for r in responses:
value = r.get('selectedValue', 'unknown')
by_value[value] = by_value.get(value, 0) + 1
print(by_value)
# {'correct': 12, 'incorrect': 6}
```
## Step 6: Form Data Analysis
### Response Aggregation
Aggregate survey responses:
```python
# Calculate average rating
ratings = [
r['responses']['engagement_rating']
for r in form_responses
]
avg_rating = sum(ratings) / len(ratings)
print(f"Average engagement: {avg_rating:.2f}/5")
```
### Cross-Tabulation
Compare responses across conditions:
```
| Condition A | Condition B | Total
--------------------|------------|-------------|-------
Robot engaged | 4.2 | 4.5 | 4.35
Natural interaction | 3.8 | 4.1 | 3.95
Would use again | 78% | 85% | 81%
```
## Step 7: Data Visualization
### Trial Timeline
Visualize trial progression:
```
P001: ████████████████░░░░░░░░░░░░░░░░░ 5:23
P002: ███████████████░░░░░░░░░░░░░░░░░░ 4:58
P003: ██████████████████████████████░░░░ 6:02
P004: ████████████████░░░░░░░░░░░░░░░░░░ 5:15
```
### Action Distribution
```
Action Frequency
────────────────
Say Text ████████████████████ 45
Wave ████████████ 25
Turn Head ████████████ 25
Move Arm ████ 5
```
### Branch Outcomes
```
Branch Selection
────────────────
Correct Response (A): ██████████████████████████ 67%
Incorrect Response (B): █████████████ 33%
```
## Step 8: Generating Reports
### Trial Summary Report
Generate PDF summary:
```
═══════════════════════════════════════════════════════════
TRIAL SUMMARY REPORT
═══════════════════════════════════════════════════════════
Study: Robot Trust Study
Participant: P001
Date: March 15, 2024
Experiment: Interactive Storyteller v1
EXECUTIVE SUMMARY
───────────────────────────────────────────────────────────
Duration: 5 minutes 23 seconds
Status: Completed successfully
Steps Completed: 6/6
Interventions: 2
TIMELINE
───────────────────────────────────────────────────────────
14:00:00 Trial started
14:00:02 Step 1: The Hook
14:00:08 Step 2: The Narrative
14:02:30 Wizard note: "Participant engaged"
14:03:00 Step 3: Comprehension Check
14:03:28 Branch selected: Correct
14:03:30 Step 4a: Correct Response
14:05:23 Trial completed
METRICS
───────────────────────────────────────────────────────────
Actions Executed: 12
Action Success Rate: 100%
Average Action Duration: 2.1s
Wizard Intervention Rate: 0.37/min
═══════════════════════════════════════════════════════════
```
### Study Report
Aggregate across participants:
```
═══════════════════════════════════════════════════════════
STUDY REPORT
═══════════════════════════════════════════════════════════
Study: Robot Trust Study
Date Range: March 1-15, 2024
Participants: 20
PARTICIPATION
───────────────────────────────────────────────────────────
Enrolled: 20
Completed: 18 (90%)
Withdrew: 1 (5%)
Failed: 1 (5%)
TIMING
───────────────────────────────────────────────────────────
Mean Duration: 5m 12s ± 28s
Min Duration: 4m 45s
Max Duration: 6m 02s
INTERVENTIONS
───────────────────────────────────────────────────────────
Total Interventions: 34
Notes: 25 (73%)
Pauses: 7 (21%)
Alerts: 2 (6%)
BRANCH SELECTION
───────────────────────────────────────────────────────────
Branch A (Correct): 12 (67%)
Branch B (Incorrect): 6 (33%)
═══════════════════════════════════════════════════════════
```
## Step 9: Data Privacy
### Anonymization
Remove identifying information:
```python
# Replace participant codes with anonymous IDs
participant_map = {
'P001': 'S001',
'P002': 'S002',
'P003': 'S003',
}
```
### Export Settings
Configure export options:
| Option | Description |
|--------|-------------|
| Include participant codes | Keep or anonymize |
| Include timestamps | Full or relative |
| Include notes | Include/exclude |
| Include form responses | Include/exclude |
## Best Practices
### Data Collection
- [ ] Enable all event logging
- [ ] Configure sensor data capture
- [ ] Set up automatic backups
- [ ] Test data export before study
### Data Storage
- [ ] Export regularly (daily/weekly)
- [ ] Store in secure location
- [ ] Follow IRB data retention
- [ ] Backup critical data
### Data Analysis
- [ ] Document analysis methods
- [ ] Track protocol versions
- [ ] Note data quality issues
- [ ] Share data dictionary
## Next Steps
Now that you understand data collection:
1. **[Your First Study](02-your-first-study.md)** - Apply data practices
2. **[Simulation Mode](09-simulation-mode.md)** - Test data collection
3. **[Running Trials](04-running-trials.md)** - Practice with data capture
---
**Previous**: [Forms & Surveys](07-forms-and-surveys.md) | **Next**: [Simulation Mode](09-simulation-mode.md)
+389
View File
@@ -0,0 +1,389 @@
# Tutorial 9: Simulation Mode
Learn how to test HRIStudio experiments without a physical robot.
## Objectives
- Enable simulation mode
- Use the mock robot server
- Test experiments end-to-end
- Practice trial execution
## Why Simulation Mode?
Simulation mode allows you to:
- **Test protocols** without a robot
- **Train wizards** before live sessions
- **Debug experiments** in development
- **Run pilots** without robot access
- **Develop** on any computer
## Understanding Simulation Options
HRIStudio offers two simulation approaches:
| Approach | Pros | Cons |
|----------|------|------|
| **Client-side** | No server needed, instant | Limited robot simulation |
| **Mock Server** | Full rosbridge protocol | Requires running server |
### Client-Side Simulation
Simulates robot locally in the browser:
- No network required
- Instant startup
- Basic action timing
- Fake sensor data
### Mock Server
Full WebSocket server simulating rosbridge:
- Complete protocol support
- Realistic timing
- Sensor data simulation
- Better for integration testing
## Step 1: Enable Client-Side Simulation
### Quick Start
1. Create or edit `hristudio/.env.local`
2. Add:
```bash
NEXT_PUBLIC_SIMULATION_MODE=true
```
3. Restart the dev server:
```bash
bun dev
```
### Verify Enabled
Look for the simulation indicator in the UI:
```
┌─────────────────────────────────────────────────────────────┐
│ Wizard Interface [🔵 SIMULATION MODE] │
├─────────────────────────────────────────────────────────────┤
```
### Features Available
In simulation mode:
- ✅ All robot actions execute (simulated timing)
- ✅ Speech actions show estimated duration
- ✅ Movement actions track position
- ✅ Sensor data is simulated
- ✅ Trial execution works normally
- ❌ Real robot not controlled
- ❌ Physical interactions not possible
## Step 2: Start Mock Robot Server
For more complete testing, use the mock server:
### Option 1: Standalone Server
```bash
cd hristudio/scripts/mock-robot
bun install
bun dev
```
Server starts on `ws://localhost:9090`
### Option 2: Docker
```bash
cd nao6-hristudio-integration
docker compose -f docker-compose.yml -f docker-compose.mock.yml --profile mock up -d
```
### Verify Server Running
```bash
# Check container
docker ps
# Should show:
# CONTAINER ID IMAGE STATUS
# abc123def456 hristudio-mock-robot Up 2 minutes
```
## Step 3: Connect to Mock Server
1. Go to the **NAO Test Page**: `/nao-test`
2. Ensure `NEXT_PUBLIC_SIMULATION_MODE` is NOT set (or set to false)
3. Click **Connect**
4. You should see:
```
Connected to rosbridge
Subscribed to: /joint_states, /bumper, /sonar/left, ...
```
## Step 4: Test Robot Actions
### From NAO Test Page
1. **Speech Test**
- Enter text: "Hello, this is a test"
- Click **Say**
- See simulated speech duration
2. **Movement Test**
- Set walk speed: 0.1 m/s
- Click **Walk Forward**
- Watch position update
3. **Head Control**
- Set yaw: 1.0, pitch: 0.0
- Click **Move Head**
- See joint angles update
### From Wizard Interface
1. Start a trial
2. Execute actions as normal
3. Actions are sent to mock server
4. Mock server responds with simulated data
## Step 5: Simulated Actions Reference
### Speech Actions
| Action | Simulation Behavior |
|--------|---------------------|
| `say_text` | Duration = 1.5s + 300ms × word_count |
| `say_with_emotion` | Duration = 1.5s + 300ms × word_count + emotion_overhead |
| `wave_goodbye` | Duration = 3.0s |
### Movement Actions
| Action | Simulation Behavior |
|--------|---------------------|
| `walk_forward` | Position updates over 500ms |
| `walk_backward` | Position updates over 500ms |
| `turn_left` | Angle decreases over 500ms |
| `turn_right` | Angle increases over 500ms |
| `stop` | Velocity set to 0 |
### Sensor Simulation
| Sensor | Simulated Value |
|--------|-----------------|
| Battery | 85% ± 2% variation |
| Joint States | Random positions ±0.1 rad |
| Bumper | False (no contact) |
| Sonar | 0.5-1.0m (random) |
| Touch | False (no touch) |
## Step 6: Testing Experiment Protocols
### Full Protocol Test
1. Enable simulation mode
2. Create or open experiment
3. Schedule trial
4. Start trial in wizard interface
5. Execute through all steps
6. Verify timing and flow
### Test Checklist
- [ ] All steps execute in order
- [ ] Branching decisions work
- [ ] Timing estimates are accurate
- [ ] Event log captures everything
- [ ] No errors or warnings
- [ ] Trial completes successfully
### Debug Mode
Enable verbose logging:
```bash
# In browser console, run:
localStorage.setItem('debug', 'true')
# Refresh page
# Now see detailed action logs in console
```
## Step 7: Training Wizards
Simulation mode is perfect for training:
### Training Scenario 1: Basic Operation
1. Enable simulation mode
2. Load simple experiment
3. Practice:
- Starting/pausing trials
- Executing quick actions
- Adding notes
### Training Scenario 2: Decision Making
1. Load branching experiment
2. Practice:
- Observing participant cues
- Selecting appropriate branches
- Documenting decisions
### Training Scenario 3: Handling Issues
1. Practice:
- Pausing for breaks
- Responding to alerts
- Stopping trials early
## Step 8: Development Workflow
### TDD with Simulation
```
┌─────────────────────────────────────────────────────────────┐
│ Development Cycle │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Design experiment in UI │
│ │ │
│ ▼ │
│ 2. Enable simulation mode │
│ │ │
│ ▼ │
│ 3. Run test trial │
│ │ │
│ ▼ │
│ 4. Review event log │
│ │ │
│ ▼ │
│ 5. Fix issues found │
│ │ │
│ └────────────┐ │
│ │ │
│ └ (repeat) │
│ │
└─────────────────────────────────────────────────────────────┘
```
### Testing Checklist
Before running real trials:
- [ ] Experiment works in simulation
- [ ] All actions execute correctly
- [ ] Timing is acceptable
- [ ] Branching works as expected
- [ ] Wizard notes function properly
- [ ] Data exports correctly
## Step 9: Transitioning to Real Robot
When ready to test with real robot:
### Step 1: Disable Simulation
Remove or set to false:
```bash
NEXT_PUBLIC_SIMULATION_MODE=false
```
### Step 2: Connect Robot
1. Start Docker services
2. Verify robot connection
3. Test with NAO Test Page
### Step 3: Run Comparison Trial
1. Run same experiment on real robot
2. Compare timing and behavior
3. Adjust parameters as needed
### Step 4: Document Differences
Note any protocol adjustments needed:
- Timing differences
- Action parameter changes
- Branch criteria updates
## Troubleshooting
### Simulation Actions Not Working
1. Check `NEXT_PUBLIC_SIMULATION_MODE=true` is set
2. Verify no errors in browser console
3. Try refreshing the page
### Mock Server Connection Failed
```bash
# Check if server is running
docker ps | grep mock
# Check server logs
docker compose logs mock_robot
# Restart if needed
docker compose restart mock_robot
```
### Actions Execute But Nothing Happens
1. Check WebSocket URL is correct
2. Verify port 9090 is not blocked
3. Try client-side simulation instead
## Comparison: Simulation vs Real
| Aspect | Simulation | Real Robot |
|--------|------------|------------|
| Setup time | 1 min | 30+ min |
| Availability | Always | Requires robot |
| Cost | Free | Robot access needed |
| Timing accuracy | Estimated | Actual |
| Physical interaction | ✗ | ✓ |
| Sensor accuracy | Fake | Real |
| Network dependent | No | Yes |
## Best Practices
### When to Use Simulation
- During experiment design
- While robot unavailable
- For wizard training
- For debugging protocols
- For quick iteration
### When to Use Real Robot
- Final protocol validation
- Timing accuracy critical
- Physical interaction matters
- Sensor data needed
- Pre-study pilot
### Transition Checklist
Before real trials:
- [ ] Protocol tested in simulation
- [ ] Timing verified
- [ ] Actions calibrated
- [ ] Wizard team trained
- [ ] Backup plan ready
## Next Steps
Now that you've mastered simulation:
1. **[Robot Integration](06-robot-integration.md)** - Connect real robot
2. **[Running Trials](04-running-trials.md)** - Execute live trials
3. **[Your First Study](02-your-first-study.md)** - Run complete study
---
**Previous**: [Data & Analysis](08-data-and-analysis.md) | **Back**: [Tutorials Overview](../tutorials/README.md)
+71
View File
@@ -0,0 +1,71 @@
# HRIStudio Tutorials
Welcome to the HRIStudio tutorials! These guides will help you get up and running with the platform for your HRI research.
## Tutorial Overview
| Tutorial | Description | Time |
|----------|-------------|------|
| **[Getting Started](tutorials/01-getting-started.md)** | Installation, setup, and first login | 10 min |
| **[Your First Study](tutorials/02-your-first-study.md)** | Creating a study and adding team members | 15 min |
| **[Designing Experiments](tutorials/03-designing-experiments.md)** | Building experiment protocols with blocks | 25 min |
| **[Running Trials](tutorials/04-running-trials.md)** | Executing trials and managing participants | 20 min |
| **[Wizard Interface](tutorials/05-wizard-interface.md)** | Real-time trial control and monitoring | 15 min |
| **[Robot Integration](tutorials/06-robot-integration.md)** | Connecting NAO6 and other robots | 20 min |
| **[Forms & Surveys](tutorials/07-forms-and-surveys.md)** | Creating consent forms and questionnaires | 15 min |
| **[Data & Analysis](tutorials/08-data-and-analysis.md)** | Collecting and exporting trial data | 15 min |
| **[Simulation Mode](tutorials/09-simulation-mode.md)** | Testing without a physical robot | 10 min |
## Quick Navigation
### For Researchers
1. [Getting Started](tutorials/01-getting-started.md) - Set up your environment
2. [Your First Study](tutorials/02-your-first-study.md) - Create your study
3. [Designing Experiments](tutorials/03-designing-experiments.md) - Build your protocol
4. [Running Trials](tutorials/04-running-trials.md) - Execute your study
5. [Data & Analysis](tutorials/08-data-and-analysis.md) - Analyze results
### For Wizards
1. [Getting Started](tutorials/01-getting-started.md) - Basic setup
2. [Wizard Interface](tutorials/05-wizard-interface.md) - Control trials
3. [Robot Integration](tutorials/06-robot-integration.md) - Connect to robot
### For Administrators
1. [Getting Started](tutorials/01-getting-started.md) - Full setup
2. [Robot Integration](tutorials/06-robot-integration.md) - Configure robots
3. [Forms & Surveys](tutorials/07-forms-and-surveys.md) - Manage templates
## Common Workflows
### Basic HRI Experiment
```
Create Study → Design Experiment → Add Participants → Run Trials → Collect Data
```
### Wizard-of-Oz Study
```
Create Study → Design Experiment with Wizard Blocks → Configure Robot →
Add Wizards → Run Trials with Live Control → Collect Data
```
### Pilot Testing
```
Create Study → Design Experiment → Enable Simulation Mode → Run Test Trials →
Refine Protocol → Connect Real Robot → Run Study
```
## Prerequisites
- **For local development**: Bun, Docker, PostgreSQL
- **For robot studies**: NAO6 robot or compatible robot
- **For cloud deployment**: Vercel, Cloudflare R2, PostgreSQL database
## Getting Help
- Check the [Quick Reference](../quick-reference.md) for common commands
- Review the [Implementation Guide](../implementation-guide.md) for technical details
- Visit the [NAO6 Integration](../nao6-quick-reference.md) for robot-specific help
---
**Next**: [Getting Started](tutorials/01-getting-started.md)
+18
View File
@@ -0,0 +1,18 @@
# Mock Robot Configuration
# Copy this file to .env and adjust as needed
# Port for mock robot WebSocket server (default: 9090, same as rosbridge)
MOCK_ROBOT_PORT=9090
# How often to publish robot state (ms)
MOCK_PUBLISH_INTERVAL=100
# Robot configuration
MOCK_ROBOT_NAME=MOCK-NAO6
MOCK_ROBOT_VERSION=6.0
MOCK_BATTERY_LEVEL=85
# Enable simulation features
MOCK_ENABLE_SPEECH=true
MOCK_ENABLE_MOVEMENT=true
MOCK_ENABLE_SENSORS=true
+21
View File
@@ -0,0 +1,21 @@
{
"name": "@hristudio/mock-robot",
"version": "1.0.0",
"description": "Mock robot server for simulating NAO6 robot connections",
"type": "module",
"main": "dist/server.js",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js"
},
"dependencies": {
"ws": "^8.16.0"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/ws": "^8.5.10",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}
+412
View File
@@ -0,0 +1,412 @@
import { WebSocketServer, WebSocket } from "ws";
interface RosMessage {
op: string;
topic?: string;
type?: string;
id?: string;
msg?: Record<string, unknown>;
service?: string;
args?: Record<string, unknown>;
}
interface Subscriber {
id: string;
topic: string;
type: string;
ws: WebSocket;
}
const PORT = parseInt(process.env.MOCK_ROBOT_PORT || "9090", 10);
const PUBLISH_INTERVAL = parseInt(process.env.MOCK_PUBLISH_INTERVAL || "100", 10);
const subscribers: Map<string, Subscriber> = new Map();
let subscriberIdCounter = 0;
const mockRobotState = {
battery: 85,
position: { x: 0, y: 0, theta: 0 },
joints: [
"HeadYaw",
"HeadPitch",
"LShoulderPitch",
"LShoulderRoll",
"LElbowYaw",
"LElbowRoll",
"LWristYaw",
"LHand",
"RShoulderPitch",
"RShoulderRoll",
"RElbowYaw",
"RElbowRoll",
"RWristYaw",
"RHand",
"LHipYawPitch",
"LHipRoll",
"LHipPitch",
"LKneePitch",
"LAnklePitch",
"LAnkleRoll",
"RHipYawPitch",
"RHipRoll",
"RHipPitch",
"RKneePitch",
"RAnklePitch",
"RAnkleRoll",
],
jointPositions: new Array(26).fill(0).map(() => (Math.random() - 0.5) * 0.1),
bumperLeft: false,
bumperRight: false,
handTouchLeft: false,
handTouchRight: false,
headTouchFront: false,
headTouchMiddle: false,
headTouchRear: false,
sonarLeft: 0.5 + Math.random() * 0.5,
sonarRight: 0.5 + Math.random() * 0.5,
lastSpeechText: "",
};
function broadcastToSubscribers(topic: string, msg: Record<string, unknown>, type: string): void {
const message = JSON.stringify({
op: "publish",
topic,
type,
msg,
});
subscribers.forEach((sub) => {
if (sub.topic === topic && sub.ws.readyState === WebSocket.OPEN) {
try {
sub.ws.send(message);
} catch (e) {
console.error(`Failed to send to subscriber ${sub.id}:`, e);
}
}
});
}
function publishRobotState(): void {
broadcastToSubscribers(
"/joint_states",
{
header: { stamp: { sec: Math.floor(Date.now() / 1000), nanosec: 0 }, frame_id: "" },
name: mockRobotState.joints,
position: mockRobotState.jointPositions,
velocity: new Array(26).fill(0),
effort: new Array(26).fill(0),
},
"sensor_msgs/JointState"
);
broadcastToSubscribers(
"/naoqi_driver/battery",
{ header: {}, percentage: mockRobotState.battery, charging: false, plug: false },
"naoqi_bridge_msgs/Bumper"
);
broadcastToSubscribers(
"/bumper",
{ left: mockRobotState.bumperLeft, right: mockRobotState.bumperRight },
"naoqi_bridge_msgs/Bumper"
);
broadcastToSubscribers(
"/hand_touch",
{
leftHand: mockRobotState.handTouchLeft,
rightHand: mockRobotState.handTouchRight,
},
"naoqi_bridge_msgs/HandTouch"
);
broadcastToSubscribers(
"/head_touch",
{
front: mockRobotState.headTouchFront,
middle: mockRobotState.headTouchMiddle,
rear: mockRobotState.headTouchRear,
},
"naoqi_bridge_msgs/HeadTouch"
);
broadcastToSubscribers(
"/sonar/left",
{ header: {}, radiation_type: 1, field_of_view: 0.5, min_range: 0.1, max_range: 5.0, range: mockRobotState.sonarLeft },
"sensor_msgs/Range"
);
broadcastToSubscribers(
"/sonar/right",
{ header: {}, radiation_type: 1, field_of_view: 0.5, min_range: 0.1, max_range: 5.0, range: mockRobotState.sonarRight },
"sensor_msgs/Range"
);
}
function handleMessage(ws: WebSocket, data: string): void {
try {
const message: RosMessage = JSON.parse(data);
console.log(`[MockRobot] Received: ${message.op} ${message.topic || message.service || ""}`);
switch (message.op) {
case "subscribe":
handleSubscribe(ws, message);
break;
case "unsubscribe":
handleUnsubscribe(message);
break;
case "publish":
handlePublish(message);
break;
case "call_service":
handleServiceCall(ws, message);
break;
case "advertise":
console.log(`[MockRobot] Client advertising: ${message.topic}`);
break;
case "unadvertise":
console.log(`[MockRobot] Client unadvertising: ${message.topic}`);
break;
case "auth":
ws.send(JSON.stringify({ op: "auth_result", result: true }));
break;
default:
console.log(`[MockRobot] Unknown operation: ${message.op}`);
}
} catch (e) {
console.error("[MockRobot] Failed to parse message:", e);
}
}
function handleSubscribe(ws: WebSocket, message: RosMessage): void {
if (!message.topic) return;
const id = `sub_${subscriberIdCounter++}`;
const subscriber: Subscriber = {
id,
topic: message.topic,
type: message.type || "unknown",
ws,
};
subscribers.set(id, subscriber);
console.log(`[MockRobot] Subscribed to ${message.topic} (${id})`);
if (message.id) {
ws.send(JSON.stringify({ op: "subscribe", id: message.id, values: true }));
}
}
function handleUnsubscribe(message: RosMessage): void {
if (!message.id) return;
const subscriber = subscribers.get(message.id);
if (subscriber) {
console.log(`[MockRobot] Unsubscribed from ${subscriber.topic}`);
subscribers.delete(message.id);
}
}
function handlePublish(message: RosMessage): void {
if (!message.topic || !message.msg) return;
console.log(`[MockRobot] Publish to ${message.topic}:`, JSON.stringify(message.msg).slice(0, 200));
if (message.topic === "/cmd_vel") {
handleCmdVel(message.msg);
} else if (message.topic === "/speech") {
handleSpeech(message.msg);
} else if (message.topic === "/joint_angles") {
handleJointAngles(message.msg);
} else if (message.topic === "/autonomous_life/control") {
handleAutonomousLife(message.msg);
} else if (message.topic === "/leds") {
handleLEDs(message.msg);
}
}
function handleCmdVel(msg: Record<string, unknown>): void {
const twist = msg as { linear?: { x?: number; y?: number; z?: number }; angular?: { x?: number; y?: number; z?: number } };
const linear = twist.linear || {};
const angular = twist.angular || {};
if (angular.z !== undefined && angular.z !== 0) {
mockRobotState.position.theta += angular.z * (PUBLISH_INTERVAL / 1000);
console.log(`[MockRobot] Turning: angular.z=${angular.z}, new theta=${mockRobotState.position.theta.toFixed(2)}`);
}
if (linear.x !== undefined && linear.x !== 0) {
const dx = linear.x * Math.cos(mockRobotState.position.theta) * (PUBLISH_INTERVAL / 1000);
const dy = linear.x * Math.sin(mockRobotState.position.theta) * (PUBLISH_INTERVAL / 1000);
mockRobotState.position.x += dx;
mockRobotState.position.y += dy;
console.log(`[MockRobot] Walking: linear.x=${linear.x}, pos=(${mockRobotState.position.x.toFixed(2)}, ${mockRobotState.position.y.toFixed(2)})`);
}
}
function handleSpeech(msg: Record<string, unknown>): void {
const text = (msg as { data?: string }).data || "";
mockRobotState.lastSpeechText = text;
console.log(`[MockRobot] Speaking: "${text}"`);
setTimeout(() => {
broadcastToSubscribers(
"/speech/status",
{ state: "done", text },
"std_msgs/String"
);
console.log(`[MockRobot] Speech complete: "${text}"`);
}, Math.max(500, text.split(/\s+/).length * 300 + 1500));
}
function handleJointAngles(msg: Record<string, unknown>): void {
const data = msg as {
joint_names?: string[];
joint_angles?: number[];
speed?: number;
};
if (data.joint_names && data.joint_angles && Array.isArray(data.joint_angles)) {
const jointAngles = data.joint_angles;
data.joint_names.forEach((name, i) => {
const idx = mockRobotState.joints.indexOf(name);
const angle = jointAngles[i];
if (idx >= 0 && angle !== undefined) {
mockRobotState.jointPositions[idx] = angle;
}
});
console.log(`[MockRobot] Joint angles updated: ${data.joint_names.join(", ")}`);
}
}
function handleAutonomousLife(msg: Record<string, unknown>): void {
const state = (msg as { data?: string }).data || "disabled";
console.log(`[MockRobot] Autonomous life: ${state}`);
}
function handleLEDs(msg: Record<string, unknown>): void {
const ledName = (msg as { name?: string }).name || "unknown";
const color = (msg as { color?: string }).color || "unknown";
console.log(`[MockRobot] LED ${ledName} set to ${color}`);
}
function handleServiceCall(ws: WebSocket, message: RosMessage): void {
const service = message.service || "";
const id = message.id || `svc_${Date.now()}`;
const args = message.args || {};
console.log(`[MockRobot] Service call: ${service}`, args);
let response: Record<string, unknown> = {};
switch (service) {
case "/rosapi/get_param":
response = { value: args.param || "" };
break;
case "/rosapi/topics_for_type":
response = { topics: [] };
break;
case "/rosapi/get_topic_type":
response = { type: "" };
break;
case "/rosapi/get_node_details":
response = { node_api: "", publications: [], subscriptions: [], services: [] };
break;
case "/naoqi_driver/get_robot_info":
response = {
robotName: "MOCK-NAO6",
robotVersion: "6.0",
bodyType: "nao",
headTiltAngle: 0,
time: Math.floor(Date.now() / 1000),
};
break;
case "/naoqi_driver/get_joint_names":
response = { joint_names: mockRobotState.joints };
break;
case "/naoqi_driver/get_position":
response = {
x: mockRobotState.position.x,
y: mockRobotState.position.y,
theta: mockRobotState.position.theta,
};
break;
case "/naoqi_driver/is_waking_up":
response = { success: true, is_waking_up: false, is_webots: false };
break;
case "/naoqi_driver/robot_supports":
response = { supports_service: true };
break;
case "/naoqi_driver/set_autonomous_state":
response = { success: true };
break;
case "/naoqi_driver/toggle_autonomous":
response = { success: true };
break;
case "/naoqi_driver/call_button_action":
response = { success: true, button_id: (args as { button_id?: string }).button_id };
break;
case "/naoqi_driver/robot_batch_request":
response = { success: true };
break;
default:
console.log(`[MockRobot] Unknown service: ${service}`);
response = { success: true };
}
ws.send(JSON.stringify({
op: "service_response",
id,
service,
result: true,
values: response,
}));
}
const wss = new WebSocketServer({ port: PORT });
console.log(`[MockRobot] Mock Robot Server starting on ws://localhost:${PORT}`);
console.log(`[MockRobot] Publish interval: ${PUBLISH_INTERVAL}ms`);
console.log("[MockRobot] Simulating NAO6 robot with rosbridge protocol\n");
wss.on("connection", (ws: WebSocket) => {
console.log("[MockRobot] Client connected");
ws.on("message", (data: Buffer) => {
handleMessage(ws, data.toString());
});
ws.on("close", () => {
console.log("[MockRobot] Client disconnected");
});
ws.on("error", (error) => {
console.error("[MockRobot] WebSocket error:", error);
});
ws.send(JSON.stringify({ op: "connected", id: "mock_robot_server" }));
});
setInterval(publishRobotState, PUBLISH_INTERVAL);
console.log(`[MockRobot] Server ready. Connect via WebSocket to ws://localhost:${PORT}`);
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
+4
View File
@@ -236,6 +236,7 @@ async function main() {
description: "A comprehensive informed consent document template for HRI research studies.", description: "A comprehensive informed consent document template for HRI research studies.",
isTemplate: true, isTemplate: true,
templateName: "Informed Consent", templateName: "Informed Consent",
version: 1,
fields: [ fields: [
{ id: "1", type: "text", label: "Study Title", required: true }, { id: "1", type: "text", label: "Study Title", required: true },
{ id: "2", type: "text", label: "Principal Investigator Name", required: true }, { id: "2", type: "text", label: "Principal Investigator Name", required: true },
@@ -261,6 +262,7 @@ async function main() {
description: "Standard questionnaire to collect participant feedback after HRI sessions.", description: "Standard questionnaire to collect participant feedback after HRI sessions.",
isTemplate: true, isTemplate: true,
templateName: "Post-Session Survey", templateName: "Post-Session Survey",
version: 2,
fields: [ fields: [
{ id: "1", type: "rating", label: "How engaging was the robot?", required: true, settings: { scale: 5 } }, { id: "1", type: "rating", label: "How engaging was the robot?", required: true, settings: { scale: 5 } },
{ id: "2", type: "rating", label: "How understandable was the robot's speech?", required: true, settings: { scale: 5 } }, { id: "2", type: "rating", label: "How understandable was the robot's speech?", required: true, settings: { scale: 5 } },
@@ -283,6 +285,7 @@ async function main() {
description: "Basic demographic information collection form.", description: "Basic demographic information collection form.",
isTemplate: true, isTemplate: true,
templateName: "Demographics", templateName: "Demographics",
version: 3,
fields: [ fields: [
{ id: "1", type: "text", label: "Age", required: true }, { id: "1", type: "text", label: "Age", required: true },
{ id: "2", type: "multiple_choice", label: "Gender", required: true, options: ["Male", "Female", "Non-binary", "Prefer not to say"] }, { id: "2", type: "multiple_choice", label: "Gender", required: true, options: ["Male", "Female", "Non-binary", "Prefer not to say"] },
@@ -303,6 +306,7 @@ async function main() {
title: "Interactive Storyteller Consent", title: "Interactive Storyteller Consent",
description: "Consent form for the Comparative WoZ Study - Interactive Storyteller scenario.", description: "Consent form for the Comparative WoZ Study - Interactive Storyteller scenario.",
active: true, active: true,
version: 4,
fields: [ fields: [
{ id: "1", type: "text", label: "Participant Name", required: true }, { id: "1", type: "text", label: "Participant Name", required: true },
{ id: "2", type: "date", label: "Date", required: true }, { id: "2", type: "date", label: "Date", required: true },
+12 -12
View File
@@ -26,9 +26,9 @@ export default function HelpCenterPage() {
description: "Learn the basics of HRIStudio and set up your first study.", description: "Learn the basics of HRIStudio and set up your first study.",
icon: BookOpen, icon: BookOpen,
items: [ items: [
{ label: "Platform Overview", href: "#" }, { label: "Tutorials Overview", href: "/help/tutorials" },
{ label: "Creating a New Study", href: "#" }, { label: "Getting Started Guide", href: "/help/tutorials/getting-started" },
{ label: "Managing Team Members", href: "#" }, { label: "Your First Study", href: "/help/tutorials/your-first-study" },
], ],
}, },
{ {
@@ -36,9 +36,9 @@ export default function HelpCenterPage() {
description: "Master the visual experiment designer and flow control.", description: "Master the visual experiment designer and flow control.",
icon: FlaskConical, icon: FlaskConical,
items: [ items: [
{ label: "Using the Visual Designer", href: "#" }, { label: "Visual Designer Guide", href: "/help/tutorials/designing-experiments" },
{ label: "Robot Actions & Plugins", href: "#" }, { label: "Robot Actions & Plugins", href: "/help/tutorials/robot-integration" },
{ label: "Variables & Logic", href: "#" }, { label: "Wizard Interface", href: "/help/tutorials/wizard-interface" },
], ],
}, },
{ {
@@ -46,9 +46,9 @@ export default function HelpCenterPage() {
description: "Execute experiments and manage Wizard of Oz sessions.", description: "Execute experiments and manage Wizard of Oz sessions.",
icon: PlayCircle, icon: PlayCircle,
items: [ items: [
{ label: "Wizard Interface Guide", href: "#" }, { label: "Running Trials Guide", href: "/help/tutorials/running-trials" },
{ label: "Participant Management", href: "#" }, { label: "Participant Management", href: "/help/tutorials/your-first-study" },
{ label: "Handling Robot Errors", href: "#" }, { label: "Simulation Mode", href: "/help/tutorials/simulation-mode" },
], ],
}, },
{ {
@@ -56,9 +56,9 @@ export default function HelpCenterPage() {
description: "Analyze trial results and export research data.", description: "Analyze trial results and export research data.",
icon: BarChart3, icon: BarChart3,
items: [ items: [
{ label: "Understanding Analytics", href: "#" }, { label: "Data & Analysis Guide", href: "/help/tutorials/data-and-analysis" },
{ label: "Exporting Data (CSV/JSON)", href: "#" }, { label: "Forms & Surveys", href: "/help/tutorials/forms-and-surveys" },
{ label: "Video Replay & Annotation", href: "#" }, { label: "Exporting Data", href: "/help/tutorials/data-and-analysis" },
], ],
}, },
]; ];
@@ -0,0 +1,196 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function DataAndAnalysisTutorial() {
return (
<TutorialPage
title="Data & Analysis"
description="Collect and export trial data"
duration="15 min"
level="Intermediate"
steps={[
{ title: "Understand data collection", description: "" },
{ title: "Access trial data", description: "" },
{ title: "Export data formats", description: "" },
{ title: "Use the analytics dashboard", description: "" },
{ title: "Generate reports", description: "" },
]}
prevTutorial={{
title: "Forms & Surveys",
href: "/help/tutorials/forms-and-surveys",
}}
nextTutorial={{
title: "Simulation Mode",
href: "/help/tutorials/simulation-mode",
}}
>
<h2>Data Collection Overview</h2>
<p>HRIStudio automatically captures comprehensive data during trials:</p>
<pre><code>Trial Data
Trial Metadata
Start/End times
Duration
Participant info
Experiment version
Event Log (Timestamped)
Step changes
Action executions
Robot responses
Wizard interventions
Form Responses
Consent forms
Surveys
Questionnaires
Sensor Data
Joint positions
Touch events
Audio/video (if enabled)</code></pre>
<h2>Event Types</h2>
<table>
<thead>
<tr><th>Event Type</th><th>Description</th><th>Data Captured</th></tr>
</thead>
<tbody>
<tr><td>trial_started</td><td>Trial began</td><td>Timestamp</td></tr>
<tr><td>step_changed</td><td>New step began</td><td>Step ID, name</td></tr>
<tr><td>action_executed</td><td>Robot action</td><td>Action details, duration</td></tr>
<tr><td>wizard_response</td><td>Wizard decision</td><td>Selected option</td></tr>
<tr><td>intervention</td><td>Wizard intervention</td><td>Type, note</td></tr>
<tr><td>trial_completed</td><td>Trial finished</td><td>Summary</td></tr>
</tbody>
</table>
<h2>Step 1: Accessing Trial Data</h2>
<h3>From Trial List</h3>
<ol>
<li>Go to <strong>Trials</strong> tab</li>
<li>Find completed trial</li>
<li>Click <strong>View Details</strong></li>
</ol>
<h3>From Study Dashboard</h3>
<ol>
<li>Open your study</li>
<li>Go to <strong>Data</strong> tab</li>
<li>Select trial or view aggregate</li>
</ol>
<h2>Step 2: Exporting Data</h2>
<h3>Export Single Trial</h3>
<ol>
<li>Open trial details</li>
<li>Click <strong>Export</strong></li>
<li>Select format</li>
</ol>
<h3>Export Study Data</h3>
<ol>
<li>Open study</li>
<li>Go to <strong>Data</strong> tab</li>
<li>Click <strong>Export All</strong></li>
<li>Select options:
<ul>
<li>Date range</li>
<li>Trial status</li>
<li>Include forms</li>
</ul>
</li>
</ol>
<h3>Export Formats</h3>
<table>
<thead>
<tr><th>Format</th><th>Contents</th></tr>
</thead>
<tbody>
<tr><td>CSV</td><td>Tabular data for spreadsheets</td></tr>
<tr><td>JSON</td><td>Full event log with metadata</td></tr>
<tr><td>Video</td><td>Screen recording (if enabled)</td></tr>
</tbody>
</table>
<h2>Step 3: Analytics Dashboard</h2>
<p>View aggregate statistics:</p>
<ul>
<li><strong>Total Trials</strong> - Number of scheduled trials</li>
<li><strong>Completed</strong> - Successfully completed trials</li>
<li><strong>Average Duration</strong> - Mean trial time</li>
<li><strong>Completion Rate</strong> - % of trials completed</li>
<li><strong>Failed</strong> - Trials that failed</li>
</ul>
<h2>Step 4: Analyzing Event Data</h2>
<h3>Timing Analysis</h3>
<p>Calculate action durations from event log:</p>
<pre><code>{`for event in events:
if event.type == 'action_executed':
duration = event.get('duration', 0)
print(f"{event.actionName}: {duration/1000:.1f}s")`}</code></pre>
<h3>Intervention Analysis</h3>
<p>Track wizard interventions:</p>
<pre><code>{`interventions = [e for e in events if e.type == 'intervention']
by_type = {}
for i in interventions:
itype = i.data.get('type', 'unknown')
by_type[itype] = by_type.get(itype, 0) + 1`}</code></pre>
<h2>Step 5: Generating Reports</h2>
<h3>Trial Summary Report</h3>
<p>Generate PDF summary with:</p>
<ul>
<li>Executive summary</li>
<li>Timeline of events</li>
<li>Metrics and statistics</li>
<li>Intervention summary</li>
</ul>
<h3>Study Report</h3>
<p>Aggregate across participants:</p>
<ul>
<li>Participation rates</li>
<li>Timing statistics</li>
<li>Intervention totals</li>
<li>Branch selection distribution</li>
</ul>
<h2>Data Privacy</h2>
<h3>Anonymization</h3>
<p>Remove identifying information:</p>
<pre><code>{`participant_map = {
'P001': 'S001',
'P002': 'S002',
'P003': 'S003',
}`}</code></pre>
<h2>Best Practices</h2>
<ul>
<li>Export data regularly (daily/weekly)</li>
<li>Store in secure location</li>
<li>Follow IRB data retention</li>
<li>Backup critical data</li>
</ul>
<div className="mt-8 flex justify-between">
<Button variant="outline" asChild>
<Link href="/help/tutorials/forms-and-surveys">
Previous: Forms & Surveys
</Link>
</Button>
<Button asChild>
<Link href="/help/tutorials/simulation-mode">
Next: Simulation Mode
</Link>
</Button>
</div>
</TutorialPage>
);
}
@@ -0,0 +1,181 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function DesigningExperimentsTutorial() {
return (
<TutorialPage
title="Designing Experiments"
description="Build experiment protocols with the visual designer"
duration="25 min"
level="Intermediate"
steps={[
{ title: "Understand the experiment structure", description: "" },
{ title: "Navigate the visual designer", description: "" },
{ title: "Use core blocks", description: "" },
{ title: "Build branching protocols", description: "" },
{ title: "Test your experiment", description: "" },
]}
prevTutorial={{
title: "Your First Study",
href: "/help/tutorials/your-first-study",
}}
nextTutorial={{
title: "Running Trials",
href: "/help/tutorials/running-trials",
}}
>
<h2>What is an Experiment?</h2>
<p>An <strong>Experiment</strong> defines the protocol for your study:</p>
<pre><code>Experiment
Steps (ordered sequence)
Actions (robot behaviors)
Wizard Blocks (human decisions)
Control Flow (loops, branches)
Robot Actions (from plugins)
Parameters (configurable values)</code></pre>
<h2>Step 1: Create an Experiment</h2>
<ol>
<li>Open your study</li>
<li>Go to <strong>Experiments</strong> tab</li>
<li>Click <strong>New Experiment</strong></li>
</ol>
<h2>Step 2: The Visual Designer</h2>
<p>The designer has three main areas:</p>
<ul>
<li><strong>Block Library</strong> (left) - Drag blocks from here</li>
<li><strong>Canvas</strong> (center) - Design your protocol visually</li>
<li><strong>Properties Panel</strong> (right) - Configure selected elements</li>
</ul>
<h2>Step 3: Block Categories</h2>
<h3>Events (Triggers)</h3>
<p>Start your experiment with these blocks:</p>
<table>
<thead>
<tr><th>Block</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>Trial Start</td><td>Triggers when trial begins</td></tr>
<tr><td>Wizard Button</td><td>Waits for wizard to press a button</td></tr>
<tr><td>Timer</td><td>Waits for a specified duration</td></tr>
<tr><td>Participant Response</td><td>Waits for participant input</td></tr>
</tbody>
</table>
<h3>Wizard Actions</h3>
<p>Blocks the wizard can control:</p>
<table>
<thead>
<tr><th>Block</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>Say Text</td><td>Robot speaks text</td></tr>
<tr><td>Play Animation</td><td>Play a predefined animation</td></tr>
<tr><td>Show Image</td><td>Display image on robot screen</td></tr>
<tr><td>Move Robot</td><td>Move robot to position</td></tr>
</tbody>
</table>
<h3>Control Flow</h3>
<p>Control experiment progression:</p>
<table>
<thead>
<tr><th>Block</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>Branch</td><td>Split into multiple paths</td></tr>
<tr><td>Loop</td><td>Repeat a sequence</td></tr>
<tr><td>Wait</td><td>Pause for duration</td></tr>
<tr><td>Converge</td><td>Merge multiple paths back</td></tr>
</tbody>
</table>
<h2>Step 4: Building a Branching Protocol</h2>
<p>Let&apos;s build &quot;The Interactive Storyteller&quot; - a simple storytelling experiment:</p>
<h3>Step 1: The Hook (Start)</h3>
<ol>
<li>Click <strong>+ Add Step</strong></li>
<li>Name it &quot;The Hook&quot;</li>
<li>Set type to <strong>Robot</strong></li>
<li>Drag <strong>Say Text</strong> block</li>
<li>Configure: <code>{`{ text: "Hello! I have a story to tell you." }`}</code></li>
</ol>
<h3>Step 2: Comprehension Check (Branching)</h3>
<ol>
<li>Add new step &quot;Comprehension Check&quot;</li>
<li>Set type to <strong>Conditional</strong></li>
<li>Add <strong>Ask Question</strong> block</li>
<li>Configure options:
<pre><code>{`{
question: "What color was the rock?",
options: [
{ label: "Correct", value: "red" },
{ label: "Incorrect", value: "other" }
]
}`}</code></pre>
</li>
<li>This creates two paths automatically</li>
</ol>
<h3>Step 3: Converge Paths</h3>
<ol>
<li>Add new step &quot;Story Continues&quot;</li>
<li>Set type to <strong>Converge</strong></li>
<li>Connect both branches to this step</li>
</ol>
<h2>Step 5: Testing Your Experiment</h2>
<h3>Preview Mode</h3>
<p>Test your experiment without running a real trial:</p>
<ol>
<li>Click <strong>Preview</strong> button</li>
<li>Step through each block</li>
<li>See timing and flow</li>
<li>Test branching decisions</li>
</ol>
<h3>Simulation Mode</h3>
<p>Run with a simulated robot:</p>
<ol>
<li>Enable <code>NEXT_PUBLIC_SIMULATION_MODE=true</code></li>
<li>Start a trial</li>
<li>Robot actions are logged but not executed</li>
</ol>
<h2>Common Patterns</h2>
<h3>Linear Protocol</h3>
<pre><code>Start Step 1 Step 2 Step 3 End</code></pre>
<h3>Branching Protocol</h3>
<pre><code>Start Step 1
Condition A Step 2a
Condition B Step 2b</code></pre>
<h3>Loop Protocol</h3>
<pre><code>Start Step 1 Loop (3x) Step 2 End
(back to Step 1)</code></pre>
<div className="mt-8 flex justify-between">
<Button variant="outline" asChild>
<Link href="/help/tutorials/your-first-study">
Previous: Your First Study
</Link>
</Button>
<Button asChild>
<Link href="/help/tutorials/running-trials">
Next: Running Trials
</Link>
</Button>
</div>
</TutorialPage>
);
}
@@ -0,0 +1,172 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function FormsAndSurveysTutorial() {
return (
<TutorialPage
title="Forms & Surveys"
description="Create consent forms and questionnaires"
duration="15 min"
level="Intermediate"
steps={[
{ title: "Understand form types", description: "" },
{ title: "Create a new form", description: "" },
{ title: "Add form fields", description: "" },
{ title: "Use form templates", description: "" },
{ title: "Collect responses", description: "" },
]}
prevTutorial={{
title: "Robot Integration",
href: "/help/tutorials/robot-integration",
}}
nextTutorial={{
title: "Data & Analysis",
href: "/help/tutorials/data-and-analysis",
}}
>
<h2>Form Types</h2>
<p>HRIStudio supports three form types:</p>
<table>
<thead>
<tr><th>Type</th><th>Purpose</th><th>When</th></tr>
</thead>
<tbody>
<tr><td>Consent</td><td>Informed consent for participation</td><td>Before trial</td></tr>
<tr><td>Survey</td><td>Collect feedback and observations</td><td>After trial</td></tr>
<tr><td>Questionnaire</td><td>Demographic data collection</td><td>Any time</td></tr>
</tbody>
</table>
<h2>Step 1: Access Forms</h2>
<ol>
<li>Go to your <strong>Study</strong></li>
<li>Click <strong>Forms</strong> tab</li>
<li>View existing forms and templates</li>
</ol>
<h2>Step 2: Create a Form</h2>
<h3>Using a Template</h3>
<ol>
<li>Click <strong>Create Form</strong></li>
<li>Select <strong>Use Template</strong></li>
<li>Choose template:
<ul>
<li>Informed Consent</li>
<li>Post-Session Survey</li>
<li>Demographics</li>
</ul>
</li>
<li>Customize as needed</li>
</ol>
<h3>From Scratch</h3>
<ol>
<li>Click <strong>Create Form</strong></li>
<li>Select <strong>Blank Form</strong></li>
<li>Choose form type</li>
<li>Build fields manually</li>
</ol>
<h2>Step 3: Form Field Types</h2>
<table>
<thead>
<tr><th>Field Type</th><th>Description</th><th>Example</th></tr>
</thead>
<tbody>
<tr><td>Text</td><td>Single line text input</td><td>Participant name</td></tr>
<tr><td>Text Area</td><td>Multi-line text</td><td>Open-ended feedback</td></tr>
<tr><td>Rating</td><td>Scale rating</td><td>Rate 1-5</td></tr>
<tr><td>Multiple Choice</td><td>Select one option</td><td>Gender selection</td></tr>
<tr><td>Yes/No</td><td>Binary choice</td><td>Consent checkbox</td></tr>
<tr><td>Date</td><td>Date picker</td><td>Session date</td></tr>
<tr><td>Signature</td><td>Digital signature</td><td>Consent signature</td></tr>
</tbody>
</table>
<h2>Step 4: Consent Forms</h2>
<p>For IRB compliance, consent forms must include:</p>
<ul>
<li>Study title and purpose</li>
<li>Principal investigator</li>
<li>Procedures description</li>
<li>Risks and benefits</li>
<li>Confidentiality statement</li>
<li>Voluntary participation note</li>
<li>Signature and date fields</li>
</ul>
<h2>Step 5: Distributing Forms</h2>
<h3>Automatic Distribution</h3>
<ol>
<li>Open form settings</li>
<li>Enable <strong>Auto-distribute</strong></li>
<li>Set trigger:
<ul>
<li>Before trial (consent)</li>
<li>After trial (survey)</li>
</ul>
</li>
<li>Select participants</li>
</ol>
<h3>Manual Distribution</h3>
<ol>
<li>Open form</li>
<li>Click <strong>Distribute</strong></li>
<li>Select participants</li>
</ol>
<h2>Step 6: Collecting Responses</h2>
<h3>View Responses</h3>
<ol>
<li>Open form</li>
<li>Click <strong>Responses</strong> tab</li>
<li>View individual submissions</li>
</ol>
<h3>Export Responses</h3>
<p>Download collected data:</p>
<table>
<thead>
<tr><th>Format</th><th>Contents</th></tr>
</thead>
<tbody>
<tr><td>CSV</td><td>Tabular data</td></tr>
<tr><td>JSON</td><td>Full response objects</td></tr>
<tr><td>PDF</td><td>Printed consent forms</td></tr>
</tbody>
</table>
<h2>Form Templates</h2>
<p>Pre-built templates available:</p>
<table>
<thead>
<tr><th>Template</th><th>Use Case</th></tr>
</thead>
<tbody>
<tr><td>Standard Consent</td><td>Generic research consent</td></tr>
<tr><td>Post-Session Survey</td><td>Post-session feedback</td></tr>
<tr><td>Demographics</td><td>Participant information</td></tr>
</tbody>
</table>
<div className="mt-8 flex justify-between">
<Button variant="outline" asChild>
<Link href="/help/tutorials/robot-integration">
Previous: Robot Integration
</Link>
</Button>
<Button asChild>
<Link href="/help/tutorials/data-and-analysis">
Next: Data & Analysis
</Link>
</Button>
</div>
</TutorialPage>
);
}
@@ -0,0 +1,143 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function GettingStartedTutorial() {
return (
<TutorialPage
title="Getting Started"
description="Set up HRIStudio and learn the basics"
duration="10 min"
level="Beginner"
steps={[
{ title: "Clone and install the repository", description: "" },
{ title: "Start the database with Docker", description: "" },
{ title: "Seed the database with sample data", description: "" },
{ title: "Start the development server", description: "" },
{ title: "Log in and explore the interface", description: "" },
]}
nextTutorial={{
title: "Your First Study",
href: "/help/tutorials/your-first-study",
}}
>
<h2>Prerequisites</h2>
<p>Before you begin, make sure you have the following installed:</p>
<ul>
<li><strong>Bun</strong> - The package manager for HRIStudio</li>
<li><strong>Docker</strong> - For running PostgreSQL and MinIO</li>
<li><strong>Git</strong> - For version control</li>
</ul>
<h2>Step 1: Clone the Repository</h2>
<p>Start by cloning the HRIStudio repository:</p>
<pre><code>git clone https://github.com/soconnor0919/hristudio.git
cd hristudio</code></pre>
<h2>Step 2: Install Dependencies</h2>
<p>HRIStudio uses Bun as its package manager:</p>
<pre><code>bun install</code></pre>
<h2>Step 3: Start the Database</h2>
<p>HRIStudio requires PostgreSQL. The easiest way is using Docker:</p>
<pre><code># Start PostgreSQL and MinIO (for file storage)
bun run docker:up
# Push database schema
bun db:push
# Seed with sample data
bun db:seed</code></pre>
<p className="bg-muted p-4 rounded-lg border">
<strong>Note:</strong> This creates the database schema and populates it with
sample users, studies, and experiments so you can explore the platform immediately.
</p>
<h2>Step 4: Start the Development Server</h2>
<pre><code>bun dev</code></pre>
<p>The application will be available at <code>http://localhost:3000</code>.</p>
<h2>Step 5: Log In</h2>
<p>Use one of the default accounts:</p>
<table>
<thead>
<tr>
<th>Role</th>
<th>Email</th>
<th>Password</th>
</tr>
</thead>
<tbody>
<tr>
<td>Administrator</td>
<td><code>sean@soconnor.dev</code></td>
<td><code>password123</code></td>
</tr>
<tr>
<td>Researcher</td>
<td><code>felipe.perrone@bucknell.edu</code></td>
<td><code>password123</code></td>
</tr>
<tr>
<td>Wizard</td>
<td><code>emily.watson@lab.edu</code></td>
<td><code>password123</code></td>
</tr>
<tr>
<td>Observer</td>
<td><code>maria.santos@tech.edu</code></td>
<td><code>password123</code></td>
</tr>
</tbody>
</table>
<h2>Exploring the Interface</h2>
<p>After logging in, you&apos;ll see the main dashboard with navigation to:</p>
<ul>
<li><strong>Studies</strong> - View and manage your research studies</li>
<li><strong>Trials</strong> - Monitor and manage experiment trials</li>
<li><strong>Plugins</strong> - Manage robot integrations</li>
<li><strong>Admin</strong> - System administration (admins only)</li>
</ul>
<h2>Using Simulation Mode</h2>
<p>If you don&apos;t have a physical robot, enable simulation mode:</p>
<ol>
<li>Create or edit <code>hristudio/.env.local</code></li>
<li>Add: <code>NEXT_PUBLIC_SIMULATION_MODE=true</code></li>
<li>Restart the dev server</li>
</ol>
<p>Simulation mode allows you to test experiments without connecting to a real robot.</p>
<h2>Troubleshooting</h2>
<h3>Database Connection Failed</h3>
<pre><code># Check if Docker is running
docker ps
# Restart the database
bun run docker:down
bun run docker:up
bun db:push</code></pre>
<h3>Port Already in Use</h3>
<p>If port 3000 is in use:</p>
<pre><code>PORT=3001 bun dev</code></pre>
<h3>Seed Script Fails</h3>
<pre><code># Reset the database
bun run docker:down -v
bun run docker:up
bun db:push
bun db:seed</code></pre>
<div className="mt-8 flex justify-end">
<Button asChild>
<Link href="/help/tutorials/your-first-study">
Next: Your First Study
</Link>
</Button>
</div>
</TutorialPage>
);
}
+241
View File
@@ -0,0 +1,241 @@
import {
BookOpen,
FlaskConical,
PlayCircle,
BarChart3,
Bot,
FileText,
ClipboardList,
Layers,
ArrowRight,
} from "lucide-react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { PageLayout } from "~/components/ui/page-layout";
import Link from "next/link";
const tutorials = [
{
slug: "getting-started",
title: "Getting Started",
description: "Set up HRIStudio and learn the basics",
icon: BookOpen,
duration: "10 min",
level: "Beginner",
href: "/help/tutorials/getting-started",
},
{
slug: "your-first-study",
title: "Your First Study",
description: "Create a research study and manage team members",
icon: Layers,
duration: "15 min",
level: "Beginner",
href: "/help/tutorials/your-first-study",
},
{
slug: "designing-experiments",
title: "Designing Experiments",
description: "Build experiment protocols with the visual designer",
icon: FlaskConical,
duration: "25 min",
level: "Intermediate",
href: "/help/tutorials/designing-experiments",
},
{
slug: "running-trials",
title: "Running Trials",
description: "Execute experiments and manage participants",
icon: PlayCircle,
duration: "20 min",
level: "Intermediate",
href: "/help/tutorials/running-trials",
},
{
slug: "wizard-interface",
title: "Wizard Interface",
description: "Real-time trial control and monitoring",
icon: Bot,
duration: "15 min",
level: "Intermediate",
href: "/help/tutorials/wizard-interface",
},
{
slug: "robot-integration",
title: "Robot Integration",
description: "Connect NAO6 and configure robot plugins",
icon: ClipboardList,
duration: "20 min",
level: "Advanced",
href: "/help/tutorials/robot-integration",
},
{
slug: "forms-and-surveys",
title: "Forms & Surveys",
description: "Create consent forms and questionnaires",
icon: FileText,
duration: "15 min",
level: "Intermediate",
href: "/help/tutorials/forms-and-surveys",
},
{
slug: "data-and-analysis",
title: "Data & Analysis",
description: "Collect and export trial data",
icon: BarChart3,
duration: "15 min",
level: "Intermediate",
href: "/help/tutorials/data-and-analysis",
},
];
const levelColors: Record<string, string> = {
Beginner: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
Intermediate: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300",
Advanced: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
};
export default function TutorialsPage() {
return (
<PageLayout
title="Tutorials"
description="Step-by-step guides for learning HRIStudio"
breadcrumb={[
{ label: "Help", href: "/help" },
{ label: "Tutorials" },
]}
>
<div className="mb-8">
<h2 className="mb-2 text-lg font-semibold">Quick Start Path</h2>
<p className="text-muted-foreground mb-4">
Follow this sequence to go from setup to running your first trial.
</p>
<div className="flex flex-wrap items-center gap-2">
{tutorials.slice(0, 5).map((tutorial, index) => (
<div key={tutorial.slug} className="flex items-center gap-2">
<Link href={tutorial.href}>
<Button variant="outline" size="sm" className="gap-2">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
{index + 1}
</span>
{tutorial.title}
</Button>
</Link>
{index < 4 && <ArrowRight className="text-muted-foreground h-4 w-4" />}
</div>
))}
</div>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{tutorials.map((tutorial) => (
<Link key={tutorial.slug} href={tutorial.href}>
<Card className="h-full transition-all hover:border-primary hover:shadow-md">
<CardHeader>
<div className="mb-2 flex items-center justify-between">
<div className="bg-primary/10 rounded-lg p-2">
<tutorial.icon className="text-primary h-5 w-5" />
</div>
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium ${levelColors[tutorial.level]}`}
>
{tutorial.level}
</span>
</div>
<CardTitle className="text-lg">{tutorial.title}</CardTitle>
<CardDescription>{tutorial.description}</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">
{tutorial.duration}
</span>
<ArrowRight className="text-muted-foreground h-4 w-4" />
</div>
</CardContent>
</Card>
</Link>
))}
</div>
<div className="mt-12">
<h2 className="mb-4 text-lg font-semibold">By Role</h2>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-base">Researchers</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
<Link href="/help/tutorials/getting-started" className="block text-muted-foreground hover:text-foreground">
Getting Started
</Link>
<Link href="/help/tutorials/your-first-study" className="block text-muted-foreground hover:text-foreground">
Your First Study
</Link>
<Link href="/help/tutorials/designing-experiments" className="block text-muted-foreground hover:text-foreground">
Designing Experiments
</Link>
<Link href="/help/tutorials/data-and-analysis" className="block text-muted-foreground hover:text-foreground">
Data & Analysis
</Link>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-base">Wizards</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
<Link href="/help/tutorials/getting-started" className="block text-muted-foreground hover:text-foreground">
Getting Started
</Link>
<Link href="/help/tutorials/wizard-interface" className="block text-muted-foreground hover:text-foreground">
Wizard Interface
</Link>
<Link href="/help/tutorials/robot-integration" className="block text-muted-foreground hover:text-foreground">
Robot Integration
</Link>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-base">Administrators</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
<Link href="/help/tutorials/getting-started" className="block text-muted-foreground hover:text-foreground">
Getting Started
</Link>
<Link href="/help/tutorials/robot-integration" className="block text-muted-foreground hover:text-foreground">
Robot Integration
</Link>
<Link href="/help/tutorials/forms-and-surveys" className="block text-muted-foreground hover:text-foreground">
Forms & Surveys
</Link>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-base">Pilot Testing</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
<Link href="/help/tutorials/getting-started" className="block text-muted-foreground hover:text-foreground">
Getting Started
</Link>
<Link href="/help/tutorials/designing-experiments" className="block text-muted-foreground hover:text-foreground">
Designing Experiments
</Link>
<Link href="/help/tutorials/running-trials" className="block text-muted-foreground hover:text-foreground">
Running Trials
</Link>
</CardContent>
</Card>
</div>
</div>
</PageLayout>
);
}
@@ -0,0 +1,182 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function RobotIntegrationTutorial() {
return (
<TutorialPage
title="Robot Integration"
description="Connect NAO6 and configure robot plugins"
duration="20 min"
level="Advanced"
steps={[
{ title: "Set up the NAO6 robot", description: "" },
{ title: "Start Docker services", description: "" },
{ title: "Configure HRIStudio", description: "" },
{ title: "Test the connection", description: "" },
{ title: "Troubleshoot common issues", description: "" },
]}
prevTutorial={{
title: "Wizard Interface",
href: "/help/tutorials/wizard-interface",
}}
nextTutorial={{
title: "Forms & Surveys",
href: "/help/tutorials/forms-and-surveys",
}}
>
<h2>Supported Robots</h2>
<p>HRIStudio supports multiple robot platforms:</p>
<table>
<thead>
<tr><th>Robot</th><th>Protocol</th><th>Capabilities</th></tr>
</thead>
<tbody>
<tr><td>NAO6</td><td>ROS2</td><td>Speech, movement, gestures, sensors</td></tr>
<tr><td>TurtleBot3</td><td>ROS2</td><td>Navigation, sensors</td></tr>
<tr><td>Mock Robot</td><td>WebSocket</td><td>All actions (simulation)</td></tr>
</tbody>
</table>
<h2>Step 1: Set Up NAO6 Robot</h2>
<h3>Network Configuration</h3>
<ol>
<li>Connect NAO6 to your network</li>
<li>Note the robot&apos;s IP address:
<pre><code># On the robot, say &quot;What is my IP address?&quot;
# Or check robot&apos;s network settings</code></pre>
</li>
<li>Verify network access:
<pre><code>ping nao.local
# Or ping the IP directly:
ping 192.168.1.100</code></pre>
</li>
</ol>
<h3>Wake Up Robot</h3>
<p>Before connecting, wake up the robot:</p>
<pre><code>ssh nao@192.168.1.100
# Enter password when prompted
# Wake up the robot
python -c &quot;from naoqi import ALProxy; proxy = ALProxy('ALMotion', '192.168.1.100', 9559); proxy.wakeUp()&quot;</code></pre>
<h2>Step 2: Start Docker Services</h2>
<pre><code>cd ~/nao6-hristudio-integration
# Set robot IP
export NAO_IP=192.168.1.100
# Start services
docker compose up -d</code></pre>
<h3>Services Overview</h3>
<table>
<thead>
<tr><th>Service</th><th>Port</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td>nao_driver</td><td>-</td><td>ROS2 driver for NAO</td></tr>
<tr><td>ros_bridge</td><td>9090</td><td>WebSocket bridge</td></tr>
<tr><td>ros_api</td><td>-</td><td>Topic introspection</td></tr>
</tbody>
</table>
<h2>Step 3: Configure HRIStudio</h2>
<h3>Install Robot Plugin</h3>
<ol>
<li>Go to <strong>Plugins</strong> in sidebar</li>
<li>Select your study</li>
<li>Click <strong>Browse Plugins</strong></li>
<li>Find <strong>NAO6 Robot (ROS2 Integration)</strong></li>
<li>Click <strong>Install</strong></li>
</ol>
<h3>Configure Plugin</h3>
<pre><code>Robot Name: NAO6-Lab
Robot IP: 192.168.1.100
WebSocket URL: ws://localhost:9090</code></pre>
<h3>Environment Variables</h3>
<p>Create <code>hristudio/.env.local</code>:</p>
<pre><code># Robot connection
NAO_ROBOT_IP=192.168.1.100
NAO_PASSWORD=robolab
NAO_USERNAME=nao
# WebSocket bridge
NEXT_PUBLIC_ROS_BRIDGE_URL=ws://localhost:9090</code></pre>
<h2>Step 4: Test Connection</h2>
<ol>
<li>Navigate to: <code>http://localhost:3000/nao-test</code></li>
<li>Click <strong>Connect</strong></li>
<li>Verify connection status shows &quot;Connected&quot;</li>
<li>Test basic actions (Say, Wave, Move)</li>
</ol>
<h2>Robot Actions Reference</h2>
<h3>Speech Actions</h3>
<table>
<thead>
<tr><th>Action</th><th>Parameters</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>say_text</td><td>text</td><td>Speak text</td></tr>
<tr><td>say_with_emotion</td><td>text, emotion</td><td>Emotional speech</td></tr>
<tr><td>set_volume</td><td>level</td><td>Set speech volume</td></tr>
</tbody>
</table>
<h3>Movement Actions</h3>
<table>
<thead>
<tr><th>Action</th><th>Parameters</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>walk_forward</td><td>speed, duration</td><td>Walk forward</td></tr>
<tr><td>walk_backward</td><td>speed</td><td>Walk backward</td></tr>
<tr><td>turn_left</td><td>speed</td><td>Turn left</td></tr>
<tr><td>turn_right</td><td>speed</td><td>Turn right</td></tr>
</tbody>
</table>
<h2>Troubleshooting</h2>
<h3>Robot Not Found</h3>
<pre><code>Error: Cannot connect to robot at 192.168.1.100
Solutions:
1. Verify IP address: ping 192.168.1.100
2. Check robot is powered on
3. Verify network connectivity
4. Try nao.local hostname</code></pre>
<h3>WebSocket Connection Failed</h3>
<pre><code>Error: WebSocket connection to ws://localhost:9090 failed
Solutions:
1. Check Docker is running: docker ps
2. Verify ros_bridge container
3. Check port 9090 is not blocked
4. Restart services: docker compose restart</code></pre>
<div className="mt-8 flex justify-between">
<Button variant="outline" asChild>
<Link href="/help/tutorials/wizard-interface">
Previous: Wizard Interface
</Link>
</Button>
<Button asChild>
<Link href="/help/tutorials/forms-and-surveys">
Next: Forms & Surveys
</Link>
</Button>
</div>
</TutorialPage>
);
}
@@ -0,0 +1,180 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function RunningTrialsTutorial() {
return (
<TutorialPage
title="Running Trials"
description="Execute experiments and manage participant trials"
duration="20 min"
level="Intermediate"
steps={[
{ title: "Schedule a trial", description: "" },
{ title: "Prepare for trial execution", description: "" },
{ title: "Start and monitor the trial", description: "" },
{ title: "Handle interventions", description: "" },
{ title: "Complete and review the trial", description: "" },
]}
prevTutorial={{
title: "Designing Experiments",
href: "/help/tutorials/designing-experiments",
}}
nextTutorial={{
title: "Wizard Interface",
href: "/help/tutorials/wizard-interface",
}}
>
<h2>What is a Trial?</h2>
<p>A <strong>Trial</strong> is a single execution of an experiment with one participant:</p>
<pre><code>Trial
Participant (who took part)
Experiment (which protocol)
Status (scheduled, in_progress, completed)
Events (timestamped actions)
Data (collected responses)</code></pre>
<h2>Trial Lifecycle</h2>
<pre><code>Scheduled In Progress Completed
Aborted
Failed </code></pre>
<h2>Step 1: Schedule a Trial</h2>
<ol>
<li>Go to your <strong>Study</strong></li>
<li>Open <strong>Trials</strong> tab</li>
<li>Click <strong>Schedule Trial</strong></li>
<li>Select:
<ul>
<li><strong>Participant</strong>: P001</li>
<li><strong>Experiment</strong>: The Interactive Storyteller</li>
<li><strong>Scheduled Time</strong>: Today, 2:00 PM</li>
</ul>
</li>
</ol>
<h2>Step 2: Prepare for Trial</h2>
<p>Before starting:</p>
<ol>
<li><strong>Verify Robot Connection</strong>
<ul>
<li>Check robot is powered on</li>
<li>Verify network connection</li>
<li>Test WebSocket connection</li>
</ul>
</li>
<li><strong>Review Experiment</strong>
<ul>
<li>Ensure experiment is &quot;Ready&quot; status</li>
<li>Check step count and timing</li>
<li>Verify all actions are configured</li>
</ul>
</li>
<li><strong>Prepare Environment</strong>
<ul>
<li>Ensure participant consent is obtained</li>
<li>Set up recording equipment (if needed)</li>
<li>Remove distractions</li>
</ul>
</li>
</ol>
<h2>Step 3: Start a Trial</h2>
<p>From Trials List:</p>
<ol>
<li>Find the scheduled trial</li>
<li>Click <strong>Start Trial</strong></li>
<li>Confirm participant is ready</li>
<li>Click <strong>Begin</strong></li>
</ol>
<h2>Step 4: During the Trial</h2>
<p>The wizard interface provides:</p>
<ul>
<li><strong>Timeline View</strong> - Visual step progression</li>
<li><strong>Current Step</strong> - Highlighted current step</li>
<li><strong>Progress</strong> - Estimated time remaining</li>
<li><strong>Event Log</strong> - Timestamped events</li>
</ul>
<h2>Step 5: Wizard Interventions</h2>
<p>During Wizard-of-Oz studies, wizards can intervene:</p>
<h3>Add Intervention</h3>
<ol>
<li>Click <strong>+ Intervention</strong></li>
<li>Select type:
<ul>
<li><strong>Pause</strong>: Temporarily stop trial</li>
<li><strong>Resume</strong>: Continue after pause</li>
<li><strong>Note</strong>: Add observation</li>
<li><strong>Alert</strong>: Send alert notification</li>
</ul>
</li>
</ol>
<h3>Branch Selection</h3>
<p>When reaching a conditional step:</p>
<ol>
<li>Observe participant response</li>
<li>Select appropriate branch</li>
<li>Selection is logged for analysis</li>
</ol>
<h2>Step 6: Trial Completion</h2>
<h3>Automatic Completion</h3>
<p>When all steps complete:</p>
<ol>
<li>Final step executes</li>
<li>Trial status &quot;Completed&quot;</li>
<li>Data is saved automatically</li>
<li>Summary shown</li>
</ol>
<h3>Manual Completion</h3>
<p>To end early:</p>
<ol>
<li>Click <strong>Stop Trial</strong></li>
<li>Confirm completion</li>
<li>Select reason</li>
<li>Save partial data</li>
</ol>
<h2>Best Practices</h2>
<h3>Before Trials</h3>
<ul className="list-disc pl-6">
<li>Robot connected and tested</li>
<li>Experiment verified</li>
<li>Participant consent obtained</li>
<li>Recording equipment ready</li>
<li>Wizard briefed on protocol</li>
</ul>
<h3>During Trials</h3>
<ul className="list-disc pl-6">
<li>Monitor timeline progress</li>
<li>Take timestamped notes</li>
<li>Document interventions</li>
<li>Watch for issues</li>
</ul>
<div className="mt-8 flex justify-between">
<Button variant="outline" asChild>
<Link href="/help/tutorials/designing-experiments">
Previous: Designing Experiments
</Link>
</Button>
<Button asChild>
<Link href="/help/tutorials/wizard-interface">
Next: Wizard Interface
</Link>
</Button>
</div>
</TutorialPage>
);
}
@@ -0,0 +1,203 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function SimulationModeTutorial() {
return (
<TutorialPage
title="Simulation Mode"
description="Test experiments without a physical robot"
duration="10 min"
level="Beginner"
steps={[
{ title: "Enable simulation mode", description: "" },
{ title: "Test robot actions", description: "" },
{ title: "Run test trials", description: "" },
{ title: "Practice wizard controls", description: "" },
{ title: "Transition to real robot", description: "" },
]}
prevTutorial={{
title: "Data & Analysis",
href: "/help/tutorials/data-and-analysis",
}}
>
<h2>Why Simulation Mode?</h2>
<p>Simulation mode allows you to:</p>
<ul>
<li><strong>Test protocols</strong> without a robot</li>
<li><strong>Train wizards</strong> before live sessions</li>
<li><strong>Debug experiments</strong> in development</li>
<li><strong>Run pilots</strong> without robot access</li>
<li><strong>Develop</strong> on any computer</li>
</ul>
<h2>Simulation Options</h2>
<p>HRIStudio offers two simulation approaches:</p>
<table>
<thead>
<tr><th>Approach</th><th>Pros</th><th>Cons</th></tr>
</thead>
<tbody>
<tr>
<td>Client-side</td>
<td>No server needed, instant</td>
<td>Limited robot simulation</td>
</tr>
<tr>
<td>Mock Server</td>
<td>Full rosbridge protocol</td>
<td>Requires running server</td>
</tr>
</tbody>
</table>
<h2>Step 1: Enable Client-Side Simulation</h2>
<h3>Quick Start</h3>
<ol>
<li>Create or edit <code>hristudio/.env.local</code></li>
<li>Add: <code>NEXT_PUBLIC_SIMULATION_MODE=true</code></li>
<li>Restart the dev server:
<pre><code>bun dev</code></pre>
</li>
</ol>
<h3>Verify Enabled</h3>
<p>Look for the simulation indicator in the UI:</p>
<pre><code>Wizard Interface [🔵 SIMULATION MODE]</code></pre>
<h2>Step 2: Start Mock Server (Optional)</h2>
<p>For more complete testing, use the mock server:</p>
<h3>Standalone Server</h3>
<pre><code>cd hristudio/scripts/mock-robot
bun install
bun dev</code></pre>
<h3>Docker</h3>
<pre><code>cd nao6-hristudio-integration
docker compose -f docker-compose.yml -f docker-compose.mock.yml --profile mock up -d</code></pre>
<h2>Step 3: Test Robot Actions</h2>
<h3>From NAO Test Page</h3>
<ol>
<li>Navigate to: <code>/nao-test</code></li>
<li>Click <strong>Connect</strong></li>
<li>Test actions:
<ul>
<li><strong>Speech</strong> - Enter text, click Say</li>
<li><strong>Movement</strong> - Set speed, click Walk</li>
<li><strong>Head</strong> - Set angles, click Move</li>
</ul>
</li>
</ol>
<h3>Simulated Actions</h3>
<table>
<thead>
<tr><th>Action</th><th>Simulation Behavior</th></tr>
</thead>
<tbody>
<tr><td>say_text</td><td>Duration = 1.5s + 300ms × word_count</td></tr>
<tr><td>walk_forward</td><td>Position updates over 500ms</td></tr>
<tr><td>turn_left/right</td><td>Angle changes over 500ms</td></tr>
</tbody>
</table>
<h2>Step 4: Run Test Trials</h2>
<ol>
<li>Enable simulation mode</li>
<li>Create or open experiment</li>
<li>Schedule trial</li>
<li>Start trial in wizard interface</li>
<li>Execute through all steps</li>
<li>Verify timing and flow</li>
</ol>
<h3>Test Checklist</h3>
<ul>
<li>All steps execute in order</li>
<li>Branching decisions work</li>
<li>Timing estimates are accurate</li>
<li>Event log captures everything</li>
<li>No errors or warnings</li>
<li>Trial completes successfully</li>
</ul>
<h2>Step 5: Training Wizards</h2>
<p>Simulation mode is perfect for training:</p>
<h3>Training Scenarios</h3>
<ol>
<li><strong>Basic Operation</strong> - Start/pause trials, execute actions</li>
<li><strong>Decision Making</strong> - Select appropriate branches</li>
<li><strong>Handling Issues</strong> - Pause, respond to alerts, stop early</li>
</ol>
<h2>Transitioning to Real Robot</h2>
<ol>
<li><strong>Disable Simulation</strong>
<pre><code>NEXT_PUBLIC_SIMULATION_MODE=false</code></pre>
</li>
<li><strong>Connect Robot</strong>
<ul>
<li>Start Docker services</li>
<li>Verify robot connection</li>
<li>Test with NAO Test Page</li>
</ul>
</li>
<li><strong>Run Comparison Trial</strong>
<ul>
<li>Run same experiment on real robot</li>
<li>Compare timing and behavior</li>
<li>Adjust parameters as needed</li>
</ul>
</li>
</ol>
<h2>Comparison: Simulation vs Real</h2>
<table>
<thead>
<tr><th>Aspect</th><th>Simulation</th><th>Real Robot</th></tr>
</thead>
<tbody>
<tr><td>Setup time</td><td>1 min</td><td>30+ min</td></tr>
<tr><td>Availability</td><td>Always</td><td>Requires robot</td></tr>
<tr><td>Cost</td><td>Free</td><td>Robot access needed</td></tr>
<tr><td>Timing accuracy</td><td>Estimated</td><td>Actual</td></tr>
<tr><td>Physical interaction</td><td></td><td></td></tr>
<tr><td>Sensor accuracy</td><td>Fake</td><td>Real</td></tr>
</tbody>
</table>
<h2>Best Practices</h2>
<h3>When to Use Simulation</h3>
<ul>
<li>During experiment design</li>
<li>While robot unavailable</li>
<li>For wizard training</li>
<li>For debugging protocols</li>
<li>For quick iteration</li>
</ul>
<h3>When to Use Real Robot</h3>
<ul>
<li>Final protocol validation</li>
<li>Timing accuracy critical</li>
<li>Physical interaction matters</li>
<li>Sensor data needed</li>
<li>Pre-study pilot</li>
</ul>
<div className="mt-8 flex justify-start">
<Button variant="outline" asChild>
<Link href="/help/tutorials/data-and-analysis">
Previous: Data & Analysis
</Link>
</Button>
</div>
</TutorialPage>
);
}
@@ -0,0 +1,179 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function WizardInterfaceTutorial() {
return (
<TutorialPage
title="Wizard Interface"
description="Real-time trial control and monitoring"
duration="15 min"
level="Intermediate"
steps={[
{ title: "Access the wizard interface", description: "" },
{ title: "Understand the layout", description: "" },
{ title: "Control robot actions", description: "" },
{ title: "Make branching decisions", description: "" },
{ title: "Handle interruptions", description: "" },
]}
prevTutorial={{
title: "Running Trials",
href: "/help/tutorials/running-trials",
}}
nextTutorial={{
title: "Robot Integration",
href: "/help/tutorials/robot-integration",
}}
>
<h2>What is the Wizard Interface?</h2>
<p>The <strong>Wizard Interface</strong> is your control center during trials. It provides:</p>
<ul>
<li>Real-time trial monitoring</li>
<li>Robot action controls</li>
<li>Decision-making tools</li>
<li>Intervention capabilities</li>
<li>Event logging</li>
</ul>
<h2>Step 1: Accessing the Interface</h2>
<h3>Method 1: From Trials List</h3>
<ol>
<li>Go to <strong>Trials</strong> in sidebar</li>
<li>Find your scheduled trial</li>
<li>Click <strong>Open Wizard</strong></li>
</ol>
<h3>Method 2: Direct URL</h3>
<pre><code>{`/trials/{trialId}/wizard`}</code></pre>
<h3>Method 3: Trial Queue</h3>
<ol>
<li>Go to <strong>Wizard Queue</strong></li>
<li>See all pending trials</li>
<li>Click <strong>Start</strong> on any trial</li>
</ol>
<h2>Step 2: Understanding the Layout</h2>
<h3>Left Panel: Trial Controls</h3>
<table>
<thead>
<tr><th>Control</th><th>Function</th></tr>
</thead>
<tbody>
<tr><td>Play/Pause</td><td>Start or pause trial</td></tr>
<tr><td>Stop</td><td>End trial early</td></tr>
<tr><td>Notes</td><td>Add timestamped observations</td></tr>
<tr><td>Alert</td><td>Send alert to researchers</td></tr>
</tbody>
</table>
<h3>Center Panel: Timeline</h3>
<ul>
<li><strong>Visual Progress</strong> - See step progression</li>
<li><strong>Current Position</strong> - Highlighted current step</li>
<li><strong>Time Display</strong> - Elapsed and estimated remaining</li>
</ul>
<h3>Right Panel: Robot Control</h3>
<ul>
<li><strong>Status Section</strong> - Connection, battery, position</li>
<li><strong>Action Section</strong> - Quick action buttons</li>
</ul>
<h2>Step 3: Controlling the Robot</h2>
<h3>Quick Actions</h3>
<p>Pre-configured robot actions:</p>
<table>
<thead>
<tr><th>Action</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td>Say Text</td><td>Make robot speak</td></tr>
<tr><td>Wave</td><td>Wave gesture</td></tr>
<tr><td>Look at Me</td><td>Turn head toward participant</td></tr>
<tr><td>Nod</td><td>Confirmation nod</td></tr>
</tbody>
</table>
<h3>Custom Say Text</h3>
<ol>
<li>Click <strong>Say Text</strong></li>
<li>Enter text in popup</li>
<li>Select options (speed, emotion)</li>
<li>Click <strong>Execute</strong></li>
</ol>
<h2>Step 4: Making Decisions</h2>
<p>When the experiment reaches a branching point:</p>
<ol>
<li><strong>Observe</strong> participant&apos;s actual response</li>
<li><strong>Consider</strong> protocol criteria</li>
<li><strong>Select</strong> appropriate branch</li>
<li><strong>Confirm</strong> selection</li>
</ol>
<p>Decision is logged with timestamp and trial continues.</p>
<h2>Step 5: Handling Interruptions</h2>
<h3>Pause Trial</h3>
<ol>
<li>Click <strong>Pause</strong> button</li>
<li>Add reason (optional)</li>
<li>Trial pauses, robot holds position</li>
</ol>
<h3>Resume Trial</h3>
<ol>
<li>Click <strong>Play</strong> button</li>
<li>Trial resumes from pause point</li>
<li>Pause duration is logged</li>
</ol>
<h3>Stop Trial</h3>
<ol>
<li>Click <strong>Stop</strong> button</li>
<li>Select reason</li>
<li>Confirm stop</li>
<li>Partial data is saved</li>
</ol>
<h2>Keyboard Shortcuts</h2>
<table>
<thead>
<tr><th>Key</th><th>Action</th></tr>
</thead>
<tbody>
<tr><td>Space</td><td>Play/Pause toggle</td></tr>
<tr><td>Escape</td><td>Stop trial</td></tr>
<tr><td>N</td><td>Add note</td></tr>
<tr><td>A</td><td>Send alert</td></tr>
</tbody>
</table>
<h2>Event Logging</h2>
<p>All actions are logged automatically:</p>
<pre><code>[14:32:05] Trial started
[14:32:08] Step 1: The Hook
[14:32:10] Action: Say Text &quot;Hello!&quot;
[14:33:28] Wizard Note: &quot;Participant engaged&quot;
[14:33:30] Branch: Correct selected
[14:34:05] Trial completed</code></pre>
<div className="mt-8 flex justify-between">
<Button variant="outline" asChild>
<Link href="/help/tutorials/running-trials">
Previous: Running Trials
</Link>
</Button>
<Button asChild>
<Link href="/help/tutorials/robot-integration">
Next: Robot Integration
</Link>
</Button>
</div>
</TutorialPage>
);
}
@@ -0,0 +1,181 @@
import { TutorialPage } from "~/components/ui/tutorial-page";
import { Button } from "~/components/ui/button";
import Link from "next/link";
export default function YourFirstStudyTutorial() {
return (
<TutorialPage
title="Your First Study"
description="Create a research study and manage team members"
duration="15 min"
level="Beginner"
steps={[
{ title: "Understand the Study structure", description: "" },
{ title: "Create a new study", description: "" },
{ title: "Add team members", description: "" },
{ title: "Install robot plugins", description: "" },
{ title: "Add participants", description: "" },
]}
prevTutorial={{
title: "Getting Started",
href: "/help/tutorials/getting-started",
}}
nextTutorial={{
title: "Designing Experiments",
href: "/help/tutorials/designing-experiments",
}}
>
<h2>What is a Study?</h2>
<p>In HRIStudio, a <strong>Study</strong> is the top-level container for your research:</p>
<pre><code>Study
Experiments (multiple protocols)
Participants (study participants)
Team Members (collaborators)
Forms & Surveys (consent, questionnaires)
Trials (individual experiment runs)</code></pre>
<h2>Step 1: Create a New Study</h2>
<ol>
<li>Log in as <strong>Researcher</strong> or <strong>Administrator</strong></li>
<li>Click <strong>Studies</strong> in the sidebar</li>
<li>Click <strong>Create Study</strong></li>
</ol>
<h3>Study Settings</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Name</td>
<td>Study title</td>
</tr>
<tr>
<td>Description</td>
<td>Brief overview of research goals</td>
</tr>
<tr>
<td>Institution</td>
<td>University or organization</td>
</tr>
<tr>
<td>IRB Protocol</td>
<td>Protocol number (e.g., 2024-HRI-001)</td>
</tr>
<tr>
<td>Status</td>
<td>Draft, Active, Completed, Archived</td>
</tr>
</tbody>
</table>
<h2>Step 2: Add Team Members</h2>
<p>Studies can have multiple collaborators with different roles:</p>
<table>
<thead>
<tr>
<th>Role</th>
<th>Permissions</th>
</tr>
</thead>
<tbody>
<tr>
<td>Owner</td>
<td>Full access, can delete study</td>
</tr>
<tr>
<td>Researcher</td>
<td>Create/edit experiments, manage participants</td>
</tr>
<tr>
<td>Wizard</td>
<td>Execute trials, control robot during trials</td>
</tr>
<tr>
<td>Observer</td>
<td>View-only access, add annotations</td>
</tr>
</tbody>
</table>
<h3>Adding a Wizard</h3>
<ol>
<li>Open your study</li>
<li>Go to <strong>Team</strong> tab</li>
<li>Click <strong>Add Member</strong></li>
<li>Enter the wizard&apos;s email</li>
<li>Select <strong>Wizard</strong> role</li>
<li>Click <strong>Invite</strong></li>
</ol>
<h2>Step 3: Install Robot Plugins</h2>
<p>For studies involving robots, you need to install the appropriate plugin:</p>
<ol>
<li>Go to <strong>Plugins</strong> in the sidebar</li>
<li>Select your study from the dropdown</li>
<li>Click <strong>Browse Plugins</strong></li>
<li>Find your robot (e.g., &quot;NAO6 Robot&quot;)</li>
<li>Click <strong>Install</strong></li>
<li>Configure robot settings (IP address, etc.)</li>
</ol>
<h2>Step 4: Add Participants</h2>
<ol>
<li>Go to <strong>Participants</strong> tab</li>
<li>Click <strong>Add Participant</strong></li>
<li>Enter participant code (e.g., &quot;P001&quot;)</li>
<li>Fill in optional details</li>
</ol>
<h3>Batch Import</h3>
<p>For large studies, import from CSV:</p>
<pre><code>participantCode,name,email,notes
P001,John Smith,john@email.com,Condition A
P002,Jane Doe,jane@email.com,Condition B</code></pre>
<h2>Study Workflow</h2>
<pre><code>Draft Active Recruiting In Progress Completed
All trials done
Trials running
Recruiting participants
Ready to collect data
Setting up study</code></pre>
<h2>Common Tasks</h2>
<h3>Clone a Study</h3>
<ol>
<li>Open the study</li>
<li>Click <strong>Settings</strong> (gear icon)</li>
<li>Select <strong>Duplicate Study</strong></li>
<li>Enter new study name</li>
</ol>
<h3>Archive a Study</h3>
<p>When a study is complete:</p>
<ol>
<li>Go to study settings</li>
<li>Change status to <strong>Archived</strong></li>
<li>Data is preserved but study is read-only</li>
</ol>
<div className="mt-8 flex justify-between">
<Button variant="outline" asChild>
<Link href="/help/tutorials/getting-started">
Previous: Getting Started
</Link>
</Button>
<Button asChild>
<Link href="/help/tutorials/designing-experiments">
Next: Designing Experiments
</Link>
</Button>
</div>
</TutorialPage>
);
}
+9 -119
View File
@@ -11,6 +11,7 @@ import {
Building, Building,
ChevronDown, ChevronDown,
FlaskConical, FlaskConical,
GraduationCap,
Home, Home,
LogOut, LogOut,
MoreHorizontal, MoreHorizontal,
@@ -59,7 +60,6 @@ import { Logo } from "~/components/ui/logo";
import { useStudyManagement } from "~/hooks/useStudyManagement"; import { useStudyManagement } from "~/hooks/useStudyManagement";
import { handleAuthError, isAuthError } from "~/lib/auth-error-handler"; import { handleAuthError, isAuthError } from "~/lib/auth-error-handler";
import { api } from "~/trpc/react";
// Global items - always available // Global items - always available
const globalItems = [ const globalItems = [
@@ -129,10 +129,9 @@ const helpItems = [
icon: BookOpen, icon: BookOpen,
}, },
{ {
title: "Interactive Tour", title: "Tutorials",
url: "#tour", url: "/help/tutorials",
icon: PlayCircle, icon: PlayCircle,
action: "tour",
}, },
]; ];
@@ -183,12 +182,6 @@ export function AppSidebar({
} }
}, [isLoadingUserStudies, selectedStudyId, userStudies, selectStudy]); }, [isLoadingUserStudies, selectedStudyId, userStudies, selectStudy]);
// Debug API call
const { data: debugData } = api.dashboard.debug.useQuery(undefined, {
enabled: process.env.NODE_ENV === "development",
staleTime: 1000 * 30, // 30 seconds
});
type Study = { type Study = {
id: string; id: string;
name: string; name: string;
@@ -285,9 +278,6 @@ export function AppSidebar({
return () => clearInterval(interval); return () => clearInterval(interval);
}, [refreshStudyData]); }, [refreshStudyData]);
// Show debug info in development
const showDebug = process.env.NODE_ENV === "development";
const [mounted, setMounted] = React.useState(false); const [mounted, setMounted] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
@@ -600,23 +590,14 @@ export function AppSidebar({
{helpItems.map((item) => { {helpItems.map((item) => {
const isActive = pathname.startsWith(item.url); const isActive = pathname.startsWith(item.url);
const menuButton = const menuButton = (
item.action === "tour" ? ( <SidebarMenuButton asChild isActive={isActive}>
<SidebarMenuButton <Link href={item.url}>
onClick={() => startTour("full_platform")}
isActive={false}
>
<item.icon className="h-4 w-4" /> <item.icon className="h-4 w-4" />
<span>{item.title}</span> <span>{item.title}</span>
</SidebarMenuButton> </Link>
) : ( </SidebarMenuButton>
<SidebarMenuButton asChild isActive={isActive}> );
<Link href={item.url}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
);
return ( return (
<SidebarMenuItem key={item.title}> <SidebarMenuItem key={item.title}>
@@ -639,99 +620,8 @@ export function AppSidebar({
</SidebarGroupContent> </SidebarGroupContent>
</SidebarGroup> </SidebarGroup>
{/* Debug info moved to footer tooltip button */}
<SidebarFooter> <SidebarFooter>
<SidebarMenu> <SidebarMenu>
{showDebug && (
<SidebarMenuItem>
{isCollapsed ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="text-muted-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex h-8 w-8 items-center justify-center rounded-md border border-transparent text-xs"
aria-label="Debug info"
>
<BarChart3 className="h-4 w-4" />
</button>
</TooltipTrigger>
<TooltipContent
side="right"
className="space-y-1 p-2 text-[10px]"
>
<div>Session: {session?.user?.email ?? "No session"}</div>
<div>Role: {userRole ?? "No role"}</div>
<div>Studies: {userStudies.length}</div>
<div>Selected: {selectedStudy?.name ?? "None"}</div>
<div>Auth: {session ? "✓" : "✗"}</div>
{debugData && (
<>
<div>DB User: {debugData.user?.email ?? "None"}</div>
<div>
System Roles:{" "}
{debugData.systemRoles.join(", ") || "None"}
</div>
<div>
Memberships: {debugData.studyMemberships.length}
</div>
<div>All Studies: {debugData.allStudies.length}</div>
<div>
Session ID: {debugData.session.userId.slice(0, 8)}
...
</div>
</>
)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton className="w-full justify-start">
<BarChart3 className="h-4 w-4" />
<span className="truncate">Debug</span>
<ChevronDown className="ml-auto h-4 w-4 flex-shrink-0" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-popper-anchor-width] max-w-72"
align="start"
>
<DropdownMenuLabel className="text-xs font-medium">
Debug Info
</DropdownMenuLabel>
<DropdownMenuSeparator />
<div className="space-y-1 px-2 py-1 text-[11px] leading-tight">
<div>Session: {session?.user?.email ?? "No session"}</div>
<div>Role: {userRole ?? "No role"}</div>
<div>Studies: {userStudies.length}</div>
<div>Selected: {selectedStudy?.name ?? "None"}</div>
<div>Auth: {session ? "✓" : "✗"}</div>
{debugData && (
<>
<div>DB User: {debugData.user?.email ?? "None"}</div>
<div>
System Roles:{" "}
{debugData.systemRoles.join(", ") || "None"}
</div>
<div>
Memberships: {debugData.studyMemberships.length}
</div>
<div>All Studies: {debugData.allStudies.length}</div>
<div>
Session ID: {debugData.session.userId.slice(0, 8)}
...
</div>
</>
)}
</div>
</DropdownMenuContent>
</DropdownMenu>
)}
</SidebarMenuItem>
)}
<SidebarMenuItem> <SidebarMenuItem>
{isCollapsed ? ( {isCollapsed ? (
<TooltipProvider> <TooltipProvider>
+126
View File
@@ -0,0 +1,126 @@
import { type ReactNode } from "react";
import Link from "next/link";
import { Button } from "~/components/ui/button";
import {
ChevronLeft,
ChevronRight,
CheckCircle2,
} from "lucide-react";
import { PageLayout } from "~/components/ui/page-layout";
interface TutorialStep {
title: string;
description: string;
}
interface TutorialPageProps {
children: ReactNode;
title: string;
description: string;
duration: string;
level: string;
steps: TutorialStep[];
prevTutorial?: {
title: string;
href: string;
};
nextTutorial?: {
title: string;
href: string;
};
}
export function TutorialPage({
children,
title,
description,
duration,
level,
steps,
prevTutorial,
nextTutorial,
}: TutorialPageProps) {
const levelColors: Record<string, string> = {
Beginner: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
Intermediate: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300",
Advanced: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
};
return (
<PageLayout
title={title}
description={description}
breadcrumb={[
{ label: "Help", href: "/help" },
{ label: "Tutorials", href: "/help/tutorials" },
{ label: title },
]}
>
<div className="grid gap-8 lg:grid-cols-[1fr_250px]">
<div className="prose prose-slate dark:prose-invert max-w-none">
{children}
</div>
<aside className="hidden lg:block">
<div className="sticky top-4 space-y-6">
<Card className="p-4">
<div className="mb-4 flex items-center justify-between">
<span className="text-sm font-medium">Tutorial Info</span>
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium ${levelColors[level]}`}
>
{level}
</span>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">Duration</span>
<span>{duration}</span>
</div>
</div>
</Card>
<div>
<h3 className="mb-3 text-sm font-medium">In This Tutorial</h3>
<ul className="space-y-2">
{steps.map((step, index) => (
<li key={index} className="flex items-start gap-2 text-sm">
<CheckCircle2 className="text-primary mt-0.5 h-4 w-4 flex-shrink-0" />
<span>{step.title}</span>
</li>
))}
</ul>
</div>
<div className="flex flex-col gap-2">
{prevTutorial && (
<Button variant="outline" size="sm" className="justify-start" asChild>
<Link href={prevTutorial.href}>
<ChevronLeft className="mr-1 h-4 w-4" />
{prevTutorial.title}
</Link>
</Button>
)}
{nextTutorial && (
<Button variant="outline" size="sm" className="justify-start" asChild>
<Link href={nextTutorial.href}>
{nextTutorial.title}
<ChevronRight className="ml-1 h-4 w-4" />
</Link>
</Button>
)}
</div>
</div>
</aside>
</div>
</PageLayout>
);
}
function Card({ children, className }: { children: ReactNode; className?: string }) {
return (
<div className={`rounded-lg border bg-card text-card-foreground shadow-sm ${className}`}>
{children}
</div>
);
}
+20 -2
View File
@@ -10,6 +10,7 @@ import {
export interface UseWizardRosOptions { export interface UseWizardRosOptions {
autoConnect?: boolean; autoConnect?: boolean;
simulationMode?: boolean;
onConnected?: () => void; onConnected?: () => void;
onDisconnected?: () => void; onDisconnected?: () => void;
onError?: (error: unknown) => void; onError?: (error: unknown) => void;
@@ -24,6 +25,7 @@ export interface UseWizardRosOptions {
export interface UseWizardRosReturn { export interface UseWizardRosReturn {
isConnected: boolean; isConnected: boolean;
isConnecting: boolean; isConnecting: boolean;
isSimulationMode: boolean;
connectionError: string | null; connectionError: string | null;
robotStatus: RobotStatus; robotStatus: RobotStatus;
activeActions: RobotActionExecution[]; activeActions: RobotActionExecution[];
@@ -48,6 +50,7 @@ export interface UseWizardRosReturn {
args?: Record<string, unknown>, args?: Record<string, unknown>,
) => Promise<any>; ) => Promise<any>;
setAutonomousLife: (enabled: boolean) => Promise<boolean>; setAutonomousLife: (enabled: boolean) => Promise<boolean>;
setSimulationMode: (enabled: boolean) => void;
} }
export function useWizardRos( export function useWizardRos(
@@ -55,6 +58,7 @@ export function useWizardRos(
): UseWizardRosReturn { ): UseWizardRosReturn {
const { const {
autoConnect = true, autoConnect = true,
simulationMode = false,
onConnected, onConnected,
onDisconnected, onDisconnected,
onError, onError,
@@ -101,14 +105,17 @@ export function useWizardRos(
// Initialize service (only once) // Initialize service (only once)
useEffect(() => { useEffect(() => {
if (!isInitializedRef.current) { if (!isInitializedRef.current) {
serviceRef.current = getWizardRosService(); serviceRef.current = getWizardRosService(simulationMode);
if (simulationMode) {
serviceRef.current.setSimulationMode(true);
}
isInitializedRef.current = true; isInitializedRef.current = true;
} }
return () => { return () => {
mountedRef.current = false; mountedRef.current = false;
}; };
}, []); }, [simulationMode]);
// Set up event listeners with stable callbacks // Set up event listeners with stable callbacks
useEffect(() => { useEffect(() => {
@@ -381,9 +388,19 @@ export function useWizardRos(
[isConnected], [isConnected],
); );
const setSimulationMode = useCallback((enabled: boolean) => {
const service = serviceRef.current;
if (service) {
service.setSimulationMode(enabled);
}
}, []);
const isSimulationMode = serviceRef.current?.isSimulationMode() ?? simulationMode;
return { return {
isConnected, isConnected,
isConnecting, isConnecting,
isSimulationMode,
connectionError, connectionError,
robotStatus, robotStatus,
activeActions, activeActions,
@@ -392,5 +409,6 @@ export function useWizardRos(
executeRobotAction, executeRobotAction,
callService, callService,
setAutonomousLife, setAutonomousLife,
setSimulationMode,
}; };
} }
+260 -5
View File
@@ -60,6 +60,10 @@ export class WizardRosService extends EventEmitter {
private maxReconnectAttempts = 5; private maxReconnectAttempts = 5;
private isConnecting = false; private isConnecting = false;
// Simulation mode
private simulationMode: boolean;
private simulationInterval: NodeJS.Timeout | null = null;
// Robot state // Robot state
private robotStatus: RobotStatus = { private robotStatus: RobotStatus = {
connected: false, connected: false,
@@ -73,15 +77,40 @@ export class WizardRosService extends EventEmitter {
// Active action tracking // Active action tracking
private activeActions: Map<string, RobotActionExecution> = new Map(); private activeActions: Map<string, RobotActionExecution> = new Map();
constructor(url: string = "ws://localhost:9090") { constructor(url: string = "ws://localhost:9090", simulationMode: boolean = false) {
super(); super();
this.url = url; this.url = url;
this.simulationMode = simulationMode ||
(typeof window !== "undefined" && process.env.NEXT_PUBLIC_SIMULATION_MODE === "true");
}
/**
* Check if running in simulation mode
*/
isSimulationMode(): boolean {
return this.simulationMode;
}
/**
* Enable or disable simulation mode
*/
setSimulationMode(enabled: boolean): void {
this.simulationMode = enabled;
if (!enabled && this.simulationInterval) {
clearInterval(this.simulationInterval);
this.simulationInterval = null;
}
} }
/** /**
* Connect to ROS bridge WebSocket * Connect to ROS bridge WebSocket
*/ */
async connect(): Promise<void> { async connect(): Promise<void> {
// Simulation mode - fake connection
if (this.simulationMode) {
return this.connectSimulation();
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if ( if (
this.isConnected || this.isConnected ||
@@ -167,6 +196,11 @@ export class WizardRosService extends EventEmitter {
disconnect(): void { disconnect(): void {
this.clearReconnectTimer(); this.clearReconnectTimer();
if (this.simulationMode) {
this.disconnectSimulation();
return;
}
if (this.ws) { if (this.ws) {
this.ws.close(1000, "Manual disconnect"); this.ws.close(1000, "Manual disconnect");
this.ws = null; this.ws = null;
@@ -178,10 +212,173 @@ export class WizardRosService extends EventEmitter {
this.emit("disconnected"); this.emit("disconnected");
} }
/**
* Simulation mode connection - simulates robot responses
*/
private async connectSimulation(): Promise<void> {
console.log(`[WizardROS] SIMULATION MODE - Connecting to mock robot`);
this.isConnected = true;
this.isConnecting = false;
this.connectionAttempts = 0;
// Initialize mock robot state
const mockStates = this.getMockJointStates();
this.robotStatus = {
connected: true,
battery: 85,
position: { x: 0, y: 0, theta: 0 },
joints: mockStates.names.reduce((acc, name, i) => {
acc[name] = mockStates.positions[i] ?? 0;
return acc;
}, {} as Record<string, number>),
sensors: {},
lastUpdate: new Date(),
};
// Start publishing simulated sensor data
this.simulationInterval = setInterval(() => {
this.publishSimulationData();
}, 100);
this.emit("connected");
console.log(`[WizardROS] SIMULATION MODE - Connected to mock robot`);
}
/**
* Simulation mode disconnection
*/
private disconnectSimulation(): void {
console.log(`[WizardROS] SIMULATION MODE - Disconnecting`);
if (this.simulationInterval) {
clearInterval(this.simulationInterval);
this.simulationInterval = null;
}
this.isConnected = false;
this.robotStatus.connected = false;
this.emit("disconnected");
}
/**
* Publish simulated sensor data
*/
private publishSimulationData(): void {
if (!this.simulationMode || !this.isConnected) return;
const mockData = this.getMockJointStates();
this.updateJointStates(mockData);
this.robotStatus.battery = 85 + Math.random() * 2 - 1; // Slight variation
this.robotStatus.sensors = {
"/bumper": { left: false, right: false },
"/hand_touch": { leftHand: false, rightHand: false },
"/head_touch": { front: false, middle: false, rear: false },
"/sonar/left": { range: 0.5 + Math.random() * 0.5 },
"/sonar/right": { range: 0.5 + Math.random() * 0.5 },
};
this.robotStatus.lastUpdate = new Date();
this.emit("robot_status_updated", this.robotStatus);
}
/**
* Get mock joint states for simulation
*/
private getMockJointStates(): { names: string[]; positions: number[] } {
const names = [
"HeadYaw", "HeadPitch",
"LShoulderPitch", "LShoulderRoll", "LElbowYaw", "LElbowRoll", "LWristYaw", "LHand",
"RShoulderPitch", "RShoulderRoll", "RElbowYaw", "RElbowRoll", "RWristYaw", "RHand",
"LHipYawPitch", "LHipRoll", "LHipPitch", "LKneePitch", "LAnklePitch", "LAnkleRoll",
"RHipYawPitch", "RHipRoll", "RHipPitch", "RKneePitch", "RAnklePitch", "RAnkleRoll",
];
const positions = names.map(() => (Math.random() - 0.5) * 0.1);
return { names, positions };
}
/**
* Execute action in simulation mode
*/
private async executeSimulationAction(
pluginName: string,
actionId: string,
parameters: Record<string, unknown>,
actionConfig?: {
topic: string;
messageType: string;
payloadMapping: {
type: string;
payload?: Record<string, unknown>;
transformFn?: string;
};
},
): Promise<RobotActionExecution> {
const executionId = `${pluginName}_${actionId}_${Date.now()}`;
const execution: RobotActionExecution = {
id: executionId,
actionId,
pluginName,
parameters,
status: "pending",
startTime: new Date(),
};
this.activeActions.set(executionId, execution);
this.emit("action_started", execution);
try {
execution.status = "executing";
this.activeActions.set(executionId, execution);
console.log(`[WizardROS] SIMULATION MODE - Executing ${actionId}:`, parameters);
// Simulate action execution based on action type
let duration = 500;
if (actionId === "say_text" || actionId === "say_with_emotion" || actionConfig?.topic === "/speech") {
const text = String(parameters.text || parameters.data || "Hello");
const wordCount = text.split(/\s+/).filter(Boolean).length;
duration = 1500 + Math.max(1000, wordCount * 300);
} else if (actionId.includes("walk") || actionId.includes("turn") || actionConfig?.topic === "/cmd_vel") {
duration = 500;
// Simulate position change
const speed = Number(parameters.speed) || 0.1;
if (actionId === "walk_forward") {
this.robotStatus.position.x += speed * 0.5;
} else if (actionId === "walk_backward") {
this.robotStatus.position.x -= speed * 0.5;
} else if (actionId === "turn_left") {
this.robotStatus.position.theta -= 0.5;
} else if (actionId === "turn_right") {
this.robotStatus.position.theta += 0.5;
}
} else if (actionId.includes("head") || actionId.includes("move") || actionConfig?.topic === "/joint_angles") {
duration = 1000;
}
// Simulate async execution
await new Promise((resolve) => setTimeout(resolve, duration));
execution.status = "completed";
execution.endTime = new Date();
this.emit("action_completed", execution);
} catch (error) {
execution.status = "failed";
execution.error = error instanceof Error ? error.message : String(error);
execution.endTime = new Date();
this.emit("action_failed", execution);
}
this.activeActions.set(executionId, execution);
return execution;
}
/** /**
* Check if connected to ROS bridge * Check if connected to ROS bridge
*/ */
getConnectionStatus(): boolean { getConnectionStatus(): boolean {
if (this.simulationMode) {
return this.isConnected;
}
return this.isConnected && this.ws?.readyState === WebSocket.OPEN; return this.isConnected && this.ws?.readyState === WebSocket.OPEN;
} }
@@ -213,6 +410,11 @@ export class WizardRosService extends EventEmitter {
throw new Error("Not connected to ROS bridge"); throw new Error("Not connected to ROS bridge");
} }
// Simulation mode - simulate action execution
if (this.simulationMode) {
return this.executeSimulationAction(pluginName, actionId, parameters, actionConfig);
}
const executionId = `${pluginName}_${actionId}_${Date.now()}`; const executionId = `${pluginName}_${actionId}_${Date.now()}`;
const execution: RobotActionExecution = { const execution: RobotActionExecution = {
id: executionId, id: executionId,
@@ -587,6 +789,42 @@ export class WizardRosService extends EventEmitter {
throw new Error("Not connected to ROS bridge"); throw new Error("Not connected to ROS bridge");
} }
// Simulation mode - return mock responses
if (this.simulationMode) {
console.log(`[WizardROS] SIMULATION MODE - Service call: ${service}`, args);
const mockResponses: Record<string, ServiceResponse> = {
"/naoqi_driver/get_robot_info": {
result: true,
values: {
robotName: "MOCK-NAO6",
robotVersion: "6.0",
bodyType: "nao",
},
},
"/naoqi_driver/get_joint_names": {
result: true,
values: {
joint_names: [
"HeadYaw", "HeadPitch", "LShoulderPitch", "LShoulderRoll",
"LElbowYaw", "LElbowRoll", "LWristYaw", "LHand",
"RShoulderPitch", "RShoulderRoll", "RElbowYaw", "RElbowRoll",
"RWristYaw", "RHand", "LHipYawPitch", "LHipRoll",
"LHipPitch", "LKneePitch", "LAnklePitch", "LAnkleRoll",
"RHipYawPitch", "RHipRoll", "RHipPitch", "RKneePitch",
"RAnklePitch", "RAnkleRoll",
],
},
},
"/naoqi_driver/get_position": {
result: true,
values: this.robotStatus.position,
},
};
return mockResponses[service] || { result: true };
}
const id = `call_${this.messageId++}`; const id = `call_${this.messageId++}`;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -892,7 +1130,7 @@ let isCreatingInstance = false;
/** /**
* Get or create the global wizard ROS service (true singleton) * Get or create the global wizard ROS service (true singleton)
*/ */
export function getWizardRosService(): WizardRosService { export function getWizardRosService(simulationMode?: boolean): WizardRosService {
// Prevent multiple instances during creation // Prevent multiple instances during creation
if (isCreatingInstance && !wizardRosService) { if (isCreatingInstance && !wizardRosService) {
throw new Error("WizardRosService is being initialized, please wait"); throw new Error("WizardRosService is being initialized, please wait");
@@ -901,7 +1139,10 @@ export function getWizardRosService(): WizardRosService {
if (!wizardRosService) { if (!wizardRosService) {
isCreatingInstance = true; isCreatingInstance = true;
try { try {
wizardRosService = new WizardRosService(); const url = typeof window !== "undefined"
? (process.env.NEXT_PUBLIC_ROS_BRIDGE_URL || "ws://localhost:9090")
: "ws://localhost:9090";
wizardRosService = new WizardRosService(url, simulationMode);
} finally { } finally {
isCreatingInstance = false; isCreatingInstance = false;
} }
@@ -912,8 +1153,12 @@ export function getWizardRosService(): WizardRosService {
/** /**
* Initialize wizard ROS service with connection * Initialize wizard ROS service with connection
*/ */
export async function initWizardRosService(): Promise<WizardRosService> { export async function initWizardRosService(simulationMode?: boolean): Promise<WizardRosService> {
const service = getWizardRosService(); const service = getWizardRosService(simulationMode);
if (simulationMode !== undefined) {
service.setSimulationMode(simulationMode);
}
if (!service.getConnectionStatus()) { if (!service.getConnectionStatus()) {
await service.connect(); await service.connect();
@@ -921,3 +1166,13 @@ export async function initWizardRosService(): Promise<WizardRosService> {
return service; return service;
} }
/**
* Reset the global wizard ROS service (useful for testing or reinitializing)
*/
export function resetWizardRosService(): void {
if (wizardRosService) {
wizardRosService.disconnect();
wizardRosService = null;
}
}