mirror of
https://github.com/soconnor0919/hristudio.git
synced 2026-05-08 05:48:56 -04:00
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:
@@ -202,6 +202,7 @@ src/
|
||||
|
||||
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
|
||||
- **[Implementation Guide](docs/implementation-guide.md)**: Technical implementation details
|
||||
- **[Project Status](docs/project-status.md)**: Current development state
|
||||
|
||||
@@ -6,11 +6,28 @@ HRIStudio is a web-based Wizard-of-Oz platform for Human-Robot Interaction resea
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| **[Tutorials](tutorials/README.md)** | Step-by-step guides for using HRIStudio |
|
||||
| **[Quick Reference](quick-reference.md)** | Essential commands, setup, troubleshooting |
|
||||
| **[Project Status](project-status.md)** | Current development state (March 2026) |
|
||||
| **[Implementation Guide](implementation-guide.md)** | Full technical implementation |
|
||||
| **[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
|
||||
|
||||
### 1. Clone & Install
|
||||
@@ -162,9 +179,22 @@ bun db:seed
|
||||
- `docs/quick-reference.md` - Commands & setup
|
||||
- `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
|
||||
- `docs/implementation-guide.md` - Full technical implementation
|
||||
- `docs/project-status.md` - Development status
|
||||
- `docs/mock-robot-simulation.md` - Robot simulation
|
||||
|
||||
### Archive (Historical)
|
||||
- `docs/_archive/` - Old documentation (outdated but preserved)
|
||||
|
||||
@@ -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()`
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
+1
-1
Submodule robot-plugins updated: d772aecc54...f83a207b16
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -236,6 +236,7 @@ async function main() {
|
||||
description: "A comprehensive informed consent document template for HRI research studies.",
|
||||
isTemplate: true,
|
||||
templateName: "Informed Consent",
|
||||
version: 1,
|
||||
fields: [
|
||||
{ id: "1", type: "text", label: "Study Title", 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.",
|
||||
isTemplate: true,
|
||||
templateName: "Post-Session Survey",
|
||||
version: 2,
|
||||
fields: [
|
||||
{ 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 } },
|
||||
@@ -283,6 +285,7 @@ async function main() {
|
||||
description: "Basic demographic information collection form.",
|
||||
isTemplate: true,
|
||||
templateName: "Demographics",
|
||||
version: 3,
|
||||
fields: [
|
||||
{ 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"] },
|
||||
@@ -303,6 +306,7 @@ async function main() {
|
||||
title: "Interactive Storyteller Consent",
|
||||
description: "Consent form for the Comparative WoZ Study - Interactive Storyteller scenario.",
|
||||
active: true,
|
||||
version: 4,
|
||||
fields: [
|
||||
{ id: "1", type: "text", label: "Participant Name", required: true },
|
||||
{ id: "2", type: "date", label: "Date", required: true },
|
||||
|
||||
@@ -26,9 +26,9 @@ export default function HelpCenterPage() {
|
||||
description: "Learn the basics of HRIStudio and set up your first study.",
|
||||
icon: BookOpen,
|
||||
items: [
|
||||
{ label: "Platform Overview", href: "#" },
|
||||
{ label: "Creating a New Study", href: "#" },
|
||||
{ label: "Managing Team Members", href: "#" },
|
||||
{ label: "Tutorials Overview", href: "/help/tutorials" },
|
||||
{ label: "Getting Started Guide", href: "/help/tutorials/getting-started" },
|
||||
{ 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.",
|
||||
icon: FlaskConical,
|
||||
items: [
|
||||
{ label: "Using the Visual Designer", href: "#" },
|
||||
{ label: "Robot Actions & Plugins", href: "#" },
|
||||
{ label: "Variables & Logic", href: "#" },
|
||||
{ label: "Visual Designer Guide", href: "/help/tutorials/designing-experiments" },
|
||||
{ label: "Robot Actions & Plugins", href: "/help/tutorials/robot-integration" },
|
||||
{ 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.",
|
||||
icon: PlayCircle,
|
||||
items: [
|
||||
{ label: "Wizard Interface Guide", href: "#" },
|
||||
{ label: "Participant Management", href: "#" },
|
||||
{ label: "Handling Robot Errors", href: "#" },
|
||||
{ label: "Running Trials Guide", href: "/help/tutorials/running-trials" },
|
||||
{ label: "Participant Management", href: "/help/tutorials/your-first-study" },
|
||||
{ label: "Simulation Mode", href: "/help/tutorials/simulation-mode" },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -56,9 +56,9 @@ export default function HelpCenterPage() {
|
||||
description: "Analyze trial results and export research data.",
|
||||
icon: BarChart3,
|
||||
items: [
|
||||
{ label: "Understanding Analytics", href: "#" },
|
||||
{ label: "Exporting Data (CSV/JSON)", href: "#" },
|
||||
{ label: "Video Replay & Annotation", href: "#" },
|
||||
{ label: "Data & Analysis Guide", href: "/help/tutorials/data-and-analysis" },
|
||||
{ label: "Forms & Surveys", href: "/help/tutorials/forms-and-surveys" },
|
||||
{ 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's build "The Interactive Storyteller" - a simple storytelling experiment:</p>
|
||||
|
||||
<h3>Step 1: The Hook (Start)</h3>
|
||||
<ol>
|
||||
<li>Click <strong>+ Add Step</strong></li>
|
||||
<li>Name it "The Hook"</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 "Comprehension Check"</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 "Story Continues"</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'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'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>
|
||||
);
|
||||
}
|
||||
@@ -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's IP address:
|
||||
<pre><code># On the robot, say "What is my IP address?"
|
||||
# Or check robot'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 "from naoqi import ALProxy; proxy = ALProxy('ALMotion', '192.168.1.100', 9559); proxy.wakeUp()"</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 "Connected"</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 "Ready" 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 → "Completed"</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'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 "Hello!"
|
||||
[14:33:28] Wizard Note: "Participant engaged"
|
||||
[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'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., "NAO6 Robot")</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., "P001")</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>
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Building,
|
||||
ChevronDown,
|
||||
FlaskConical,
|
||||
GraduationCap,
|
||||
Home,
|
||||
LogOut,
|
||||
MoreHorizontal,
|
||||
@@ -59,7 +60,6 @@ import { Logo } from "~/components/ui/logo";
|
||||
|
||||
import { useStudyManagement } from "~/hooks/useStudyManagement";
|
||||
import { handleAuthError, isAuthError } from "~/lib/auth-error-handler";
|
||||
import { api } from "~/trpc/react";
|
||||
|
||||
// Global items - always available
|
||||
const globalItems = [
|
||||
@@ -129,10 +129,9 @@ const helpItems = [
|
||||
icon: BookOpen,
|
||||
},
|
||||
{
|
||||
title: "Interactive Tour",
|
||||
url: "#tour",
|
||||
title: "Tutorials",
|
||||
url: "/help/tutorials",
|
||||
icon: PlayCircle,
|
||||
action: "tour",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -183,12 +182,6 @@ export function AppSidebar({
|
||||
}
|
||||
}, [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 = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -285,9 +278,6 @@ export function AppSidebar({
|
||||
return () => clearInterval(interval);
|
||||
}, [refreshStudyData]);
|
||||
|
||||
// Show debug info in development
|
||||
const showDebug = process.env.NODE_ENV === "development";
|
||||
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -600,16 +590,7 @@ export function AppSidebar({
|
||||
{helpItems.map((item) => {
|
||||
const isActive = pathname.startsWith(item.url);
|
||||
|
||||
const menuButton =
|
||||
item.action === "tour" ? (
|
||||
<SidebarMenuButton
|
||||
onClick={() => startTour("full_platform")}
|
||||
isActive={false}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
<span>{item.title}</span>
|
||||
</SidebarMenuButton>
|
||||
) : (
|
||||
const menuButton = (
|
||||
<SidebarMenuButton asChild isActive={isActive}>
|
||||
<Link href={item.url}>
|
||||
<item.icon className="h-4 w-4" />
|
||||
@@ -639,99 +620,8 @@ export function AppSidebar({
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
|
||||
{/* Debug info moved to footer tooltip button */}
|
||||
|
||||
<SidebarFooter>
|
||||
<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>
|
||||
{isCollapsed ? (
|
||||
<TooltipProvider>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
|
||||
export interface UseWizardRosOptions {
|
||||
autoConnect?: boolean;
|
||||
simulationMode?: boolean;
|
||||
onConnected?: () => void;
|
||||
onDisconnected?: () => void;
|
||||
onError?: (error: unknown) => void;
|
||||
@@ -24,6 +25,7 @@ export interface UseWizardRosOptions {
|
||||
export interface UseWizardRosReturn {
|
||||
isConnected: boolean;
|
||||
isConnecting: boolean;
|
||||
isSimulationMode: boolean;
|
||||
connectionError: string | null;
|
||||
robotStatus: RobotStatus;
|
||||
activeActions: RobotActionExecution[];
|
||||
@@ -48,6 +50,7 @@ export interface UseWizardRosReturn {
|
||||
args?: Record<string, unknown>,
|
||||
) => Promise<any>;
|
||||
setAutonomousLife: (enabled: boolean) => Promise<boolean>;
|
||||
setSimulationMode: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export function useWizardRos(
|
||||
@@ -55,6 +58,7 @@ export function useWizardRos(
|
||||
): UseWizardRosReturn {
|
||||
const {
|
||||
autoConnect = true,
|
||||
simulationMode = false,
|
||||
onConnected,
|
||||
onDisconnected,
|
||||
onError,
|
||||
@@ -101,14 +105,17 @@ export function useWizardRos(
|
||||
// Initialize service (only once)
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current) {
|
||||
serviceRef.current = getWizardRosService();
|
||||
serviceRef.current = getWizardRosService(simulationMode);
|
||||
if (simulationMode) {
|
||||
serviceRef.current.setSimulationMode(true);
|
||||
}
|
||||
isInitializedRef.current = true;
|
||||
}
|
||||
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
}, [simulationMode]);
|
||||
|
||||
// Set up event listeners with stable callbacks
|
||||
useEffect(() => {
|
||||
@@ -381,9 +388,19 @@ export function useWizardRos(
|
||||
[isConnected],
|
||||
);
|
||||
|
||||
const setSimulationMode = useCallback((enabled: boolean) => {
|
||||
const service = serviceRef.current;
|
||||
if (service) {
|
||||
service.setSimulationMode(enabled);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isSimulationMode = serviceRef.current?.isSimulationMode() ?? simulationMode;
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
isConnecting,
|
||||
isSimulationMode,
|
||||
connectionError,
|
||||
robotStatus,
|
||||
activeActions,
|
||||
@@ -392,5 +409,6 @@ export function useWizardRos(
|
||||
executeRobotAction,
|
||||
callService,
|
||||
setAutonomousLife,
|
||||
setSimulationMode,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ export class WizardRosService extends EventEmitter {
|
||||
private maxReconnectAttempts = 5;
|
||||
private isConnecting = false;
|
||||
|
||||
// Simulation mode
|
||||
private simulationMode: boolean;
|
||||
private simulationInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
// Robot state
|
||||
private robotStatus: RobotStatus = {
|
||||
connected: false,
|
||||
@@ -73,15 +77,40 @@ export class WizardRosService extends EventEmitter {
|
||||
// Active action tracking
|
||||
private activeActions: Map<string, RobotActionExecution> = new Map();
|
||||
|
||||
constructor(url: string = "ws://localhost:9090") {
|
||||
constructor(url: string = "ws://localhost:9090", simulationMode: boolean = false) {
|
||||
super();
|
||||
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
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
// Simulation mode - fake connection
|
||||
if (this.simulationMode) {
|
||||
return this.connectSimulation();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (
|
||||
this.isConnected ||
|
||||
@@ -167,6 +196,11 @@ export class WizardRosService extends EventEmitter {
|
||||
disconnect(): void {
|
||||
this.clearReconnectTimer();
|
||||
|
||||
if (this.simulationMode) {
|
||||
this.disconnectSimulation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close(1000, "Manual disconnect");
|
||||
this.ws = null;
|
||||
@@ -178,10 +212,173 @@ export class WizardRosService extends EventEmitter {
|
||||
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
|
||||
*/
|
||||
getConnectionStatus(): boolean {
|
||||
if (this.simulationMode) {
|
||||
return this.isConnected;
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
// Simulation mode - simulate action execution
|
||||
if (this.simulationMode) {
|
||||
return this.executeSimulationAction(pluginName, actionId, parameters, actionConfig);
|
||||
}
|
||||
|
||||
const executionId = `${pluginName}_${actionId}_${Date.now()}`;
|
||||
const execution: RobotActionExecution = {
|
||||
id: executionId,
|
||||
@@ -587,6 +789,42 @@ export class WizardRosService extends EventEmitter {
|
||||
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++}`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -892,7 +1130,7 @@ let isCreatingInstance = false;
|
||||
/**
|
||||
* 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
|
||||
if (isCreatingInstance && !wizardRosService) {
|
||||
throw new Error("WizardRosService is being initialized, please wait");
|
||||
@@ -901,7 +1139,10 @@ export function getWizardRosService(): WizardRosService {
|
||||
if (!wizardRosService) {
|
||||
isCreatingInstance = true;
|
||||
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 {
|
||||
isCreatingInstance = false;
|
||||
}
|
||||
@@ -912,8 +1153,12 @@ export function getWizardRosService(): WizardRosService {
|
||||
/**
|
||||
* Initialize wizard ROS service with connection
|
||||
*/
|
||||
export async function initWizardRosService(): Promise<WizardRosService> {
|
||||
const service = getWizardRosService();
|
||||
export async function initWizardRosService(simulationMode?: boolean): Promise<WizardRosService> {
|
||||
const service = getWizardRosService(simulationMode);
|
||||
|
||||
if (simulationMode !== undefined) {
|
||||
service.setSimulationMode(simulationMode);
|
||||
}
|
||||
|
||||
if (!service.getConnectionStatus()) {
|
||||
await service.connect();
|
||||
@@ -921,3 +1166,13 @@ export async function initWizardRosService(): Promise<WizardRosService> {
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the global wizard ROS service (useful for testing or reinitializing)
|
||||
*/
|
||||
export function resetWizardRosService(): void {
|
||||
if (wizardRosService) {
|
||||
wizardRosService.disconnect();
|
||||
wizardRosService = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user