Files
hristudio/docs/plugin-system-implementation-guide.md
2025-08-07 01:12:58 -04:00

18 KiB

HRIStudio Plugin System Implementation Guide

Overview

This guide provides step-by-step instructions for implementing the HRIStudio Plugin System integration. You have access to two repositories:

  1. HRIStudio Main Repository - Contains the core platform
  2. Plugin Repository - Contains robot plugin definitions and web interface

Your task is to create a plugin store within HRIStudio and modify the plugin repository to ensure seamless integration.

Architecture Overview

HRIStudio Platform
├── Plugin Store (Frontend)
├── Plugin Manager (Backend)
├── Plugin Registry (Database)
└── ROS2 Integration Layer
    └── Plugin Repository (External)
        ├── Repository Metadata
        ├── Plugin Definitions
        └── Web Interface

Phase 1: Plugin Store Frontend Implementation

1.1 Create Plugin Store Page

Location: src/app/(dashboard)/plugins/page.tsx

Create a new page that displays available plugins from registered repositories.

// Key features to implement:
- Repository management (add/remove plugin repositories)
- Plugin browsing with categories and search
- Plugin details modal/page
- Installation status tracking
- Trust level indicators (Official, Verified, Community)

UI Requirements:

  • Use existing HRIStudio design system (shadcn/ui)
  • Follow established patterns from studies/experiments pages
  • Include plugin cards with thumbnails, descriptions, and metadata
  • Implement filtering by category, platform (ROS2), trust level
  • Add search functionality

1.2 Plugin Repository Management

Location: src/components/plugins/repository-manager.tsx

// Features to implement:
- Add repository by URL
- Validate repository structure
- Display repository metadata (name, trust level, plugin count)
- Enable/disable repositories
- Remove repositories
- Repository status indicators (online, offline, error)

1.3 Plugin Installation Interface

Location: src/components/plugins/plugin-installer.tsx

// Features to implement:
- Plugin installation progress
- Dependency checking
- Version compatibility validation
- Installation success/error handling
- Plugin configuration interface

Phase 2: Plugin Manager Backend Implementation

2.1 Database Schema Extensions

Location: src/server/db/schema/plugins.ts

Add these tables to the existing schema:

-- Plugin repositories
CREATE TABLE plugin_repositories (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(255) NOT NULL,
  url TEXT NOT NULL UNIQUE,
  trust_level VARCHAR(20) NOT NULL CHECK (trust_level IN ('official', 'verified', 'community')),
  enabled BOOLEAN DEFAULT true,
  last_synced TIMESTAMP,
  metadata JSONB DEFAULT '{}',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Installed plugins
CREATE TABLE installed_plugins (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  repository_id UUID NOT NULL REFERENCES plugin_repositories(id) ON DELETE CASCADE,
  plugin_id VARCHAR(255) NOT NULL, -- robotId from plugin definition
  name VARCHAR(255) NOT NULL,
  version VARCHAR(50) NOT NULL,
  configuration JSONB DEFAULT '{}',
  enabled BOOLEAN DEFAULT true,
  installed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  installed_by UUID NOT NULL REFERENCES users(id),
  UNIQUE(repository_id, plugin_id)
);

-- Plugin usage in studies
CREATE TABLE study_plugins (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  study_id UUID NOT NULL REFERENCES studies(id) ON DELETE CASCADE,
  installed_plugin_id UUID NOT NULL REFERENCES installed_plugins(id),
  configuration JSONB DEFAULT '{}',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  UNIQUE(study_id, installed_plugin_id)
);

2.2 tRPC Routes Implementation

Location: src/server/api/routers/plugins.ts

export const pluginsRouter = createTRPCRouter({
  // Repository management
  addRepository: protectedProcedure
    .input(z.object({
      url: z.string().url(),
      name: z.string().optional()
    }))
    .mutation(async ({ ctx, input }) => {
      // Validate repository structure
      // Add to database
      // Sync plugins
    }),

  listRepositories: protectedProcedure
    .query(async ({ ctx }) => {
      // Return user's accessible repositories
    }),

  syncRepository: protectedProcedure
    .input(z.object({ repositoryId: z.string().uuid() }))
    .mutation(async ({ ctx, input }) => {
      // Fetch repository.json
      // Update plugin definitions
      // Handle errors
    }),

  // Plugin management
  listAvailablePlugins: protectedProcedure
    .input(z.object({
      repositoryId: z.string().uuid().optional(),
      search: z.string().optional(),
      category: z.string().optional(),
      platform: z.string().optional()
    }))
    .query(async ({ ctx, input }) => {
      // Fetch plugins from repositories
      // Apply filters
      // Return plugin metadata
    }),

  installPlugin: protectedProcedure
    .input(z.object({
      repositoryId: z.string().uuid(),
      pluginId: z.string(),
      configuration: z.record(z.any()).optional()
    }))
    .mutation(async ({ ctx, input }) => {
      // Validate plugin compatibility
      // Install plugin
      // Create plugin instance
    }),

  listInstalledPlugins: protectedProcedure
    .query(async ({ ctx }) => {
      // Return user's installed plugins
    }),

  getPluginActions: protectedProcedure
    .input(z.object({ pluginId: z.string() }))
    .query(async ({ ctx, input }) => {
      // Return plugin action definitions
      // For use in experiment designer
    })
});

2.3 Plugin Registry Service

Location: src/lib/plugins/registry.ts

export class PluginRegistry {
  // Fetch and validate repository metadata
  async fetchRepository(url: string): Promise<RepositoryMetadata>
  
  // Sync plugins from repository
  async syncRepository(repositoryId: string): Promise<void>
  
  // Load plugin definition
  async loadPlugin(repositoryId: string, pluginId: string): Promise<PluginDefinition>
  
  // Validate plugin compatibility
  async validatePlugin(plugin: PluginDefinition): Promise<ValidationResult>
  
  // Install plugin
  async installPlugin(repositoryId: string, pluginId: string, config?: any): Promise<void>
}

Phase 3: Plugin Repository Modifications

3.1 Schema Enhancements

Location: Plugin Repository - docs/schema.md

Update the plugin schema to include HRIStudio-specific fields:

{
  "robotId": "string (required)",
  "name": "string (required)", 
  "description": "string (optional)",
  "platform": "string (required)",
  "version": "string (required)",
  
  // Add these HRIStudio-specific fields:
  "pluginApiVersion": "string (required) - Plugin API version",
  "hriStudioVersion": "string (required) - Minimum HRIStudio version", 
  "trustLevel": "string (enum: official|verified|community)",
  "category": "string (required) - Plugin category for UI organization",
  
  // Enhanced action schema:
  "actions": [
    {
      "id": "string (required) - Unique action identifier",
      "name": "string (required) - Display name",
      "description": "string (optional)",
      "category": "string (required) - movement|interaction|sensors|logic",
      "icon": "string (optional) - Lucide icon name",
      "timeout": "number (optional) - Default timeout in milliseconds",
      "retryable": "boolean (optional) - Can this action be retried on failure",
      
      "parameterSchema": {
        "type": "object",
        "properties": {
          // Zod-compatible parameter definitions
        },
        "required": ["array of required parameter names"]
      },
      
      "ros2": {
        // Existing ROS2 configuration
      }
    }
  ]
}

3.2 TurtleBot3 Plugin Update

Location: Plugin Repository - plugins/turtlebot3-burger.json

Add the missing HRIStudio fields to the existing plugin:

{
  "robotId": "turtlebot3-burger",
  "name": "TurtleBot3 Burger",
  "description": "A compact, affordable, programmable, ROS2-based mobile robot for education and research",
  "platform": "ROS2",
  "version": "2.0.0",
  
  // Add these new fields:
  "pluginApiVersion": "1.0",
  "hriStudioVersion": ">=0.1.0",
  "trustLevel": "official",
  "category": "mobile-robot",
  
  // Update actions with HRIStudio fields:
  "actions": [
    {
      "id": "move_velocity", // Changed from actionId
      "name": "Set Velocity", // Changed from title
      "description": "Control the robot's linear and angular velocity",
      "category": "movement", // New field
      "icon": "navigation", // New field
      "timeout": 30000, // New field
      "retryable": true, // New field
      
      "parameterSchema": {
        // Convert existing parameters to HRIStudio format
        "type": "object",
        "properties": {
          "linear": {
            "type": "number",
            "minimum": -0.22,
            "maximum": 0.22,
            "default": 0,
            "description": "Forward/backward velocity in m/s"
          },
          "angular": {
            "type": "number",
            "minimum": -2.84,
            "maximum": 2.84,
            "default": 0,
            "description": "Rotational velocity in rad/s"
          }
        },
        "required": ["linear", "angular"]
      },
      
      // Keep existing ros2 config
      "ros2": {
        "messageType": "geometry_msgs/msg/Twist",
        "topic": "/cmd_vel",
        "payloadMapping": {
          "type": "transform",
          "transformFn": "transformToTwist"
        },
        "qos": {
          "reliability": "reliable",
          "durability": "volatile",
          "history": "keep_last",
          "depth": 1
        }
      }
    }
  ]
}

3.3 Repository Metadata Update

Location: Plugin Repository - repository.json

Add HRIStudio-specific metadata:

{
  "id": "hristudio-official",
  "name": "HRIStudio Official Robot Plugins",
  "description": "Official collection of robot plugins maintained by the HRIStudio team",
  
  // Add API versioning:
  "apiVersion": "1.0",
  "pluginApiVersion": "1.0",
  
  // Add plugin categories:
  "categories": [
    {
      "id": "mobile-robots",
      "name": "Mobile Robots", 
      "description": "Wheeled and tracked mobile platforms"
    },
    {
      "id": "manipulators",
      "name": "Manipulators",
      "description": "Robotic arms and end effectors"
    },
    {
      "id": "humanoids", 
      "name": "Humanoid Robots",
      "description": "Human-like robots for social interaction"
    },
    {
      "id": "drones",
      "name": "Aerial Vehicles",
      "description": "Quadcopters and fixed-wing UAVs"
    }
  ],
  
  // Keep existing fields...
  "compatibility": {
    "hristudio": {
      "min": "0.1.0",
      "recommended": "0.1.0"
    },
    "ros2": {
      "distributions": ["humble", "iron"],
      "recommended": "iron"
    }
  }
}

Phase 4: Integration Implementation

4.1 Experiment Designer Integration

Location: HRIStudio - src/components/experiments/designer/EnhancedBlockDesigner.tsx

Add plugin-based action loading to the block designer:

// In the block registry, load actions from installed plugins:
const loadPluginActions = async (studyId: string) => {
  const installedPlugins = await api.plugins.getStudyPlugins.query({ studyId });
  
  for (const plugin of installedPlugins) {
    const actions = await api.plugins.getPluginActions.query({ 
      pluginId: plugin.id 
    });
    
    // Register actions in block registry
    actions.forEach(action => {
      blockRegistry.register({
        id: `${plugin.id}.${action.id}`,
        name: action.name,
        description: action.description,
        category: action.category,
        icon: action.icon || 'bot',
        shape: 'action',
        color: getCategoryColor(action.category),
        parameters: convertToZodSchema(action.parameterSchema),
        metadata: {
          pluginId: plugin.id,
          robotId: plugin.robotId,
          ros2Config: action.ros2
        }
      });
    });
  }
};

4.2 Trial Execution Integration

Location: HRIStudio - src/lib/plugins/execution.ts

Create plugin execution interface:

export class PluginExecutor {
  private installedPlugins = new Map<string, InstalledPlugin>();
  private rosConnections = new Map<string, RosConnection>();

  async executePluginAction(
    pluginId: string, 
    actionId: string, 
    parameters: Record<string, any>
  ): Promise<ActionResult> {
    const plugin = this.installedPlugins.get(pluginId);
    if (!plugin) {
      throw new Error(`Plugin ${pluginId} not found`);
    }

    const action = plugin.actions.find(a => a.id === actionId);
    if (!action) {
      throw new Error(`Action ${actionId} not found in plugin ${pluginId}`);
    }

    // Validate parameters against schema
    const validation = this.validateParameters(action.parameterSchema, parameters);
    if (!validation.success) {
      throw new Error(`Parameter validation failed: ${validation.error}`);
    }

    // Execute via ROS2 if configured
    if (action.ros2) {
      return this.executeRos2Action(plugin, action, parameters);
    }

    // Execute via REST API if configured
    if (action.rest) {
      return this.executeRestAction(plugin, action, parameters);
    }

    throw new Error(`No execution method configured for action ${actionId}`);
  }

  private async executeRos2Action(
    plugin: InstalledPlugin,
    action: PluginAction, 
    parameters: Record<string, any>
  ): Promise<ActionResult> {
    const connection = this.getRosConnection(plugin.id);
    
    // Transform parameters according to payload mapping
    const payload = this.transformPayload(action.ros2.payloadMapping, parameters);
    
    // Publish to topic or call service
    if (action.ros2.topic) {
      return this.publishToTopic(connection, action.ros2, payload);
    } else if (action.ros2.service) {
      return this.callService(connection, action.ros2, payload);
    } else if (action.ros2.action) {
      return this.executeAction(connection, action.ros2, payload);
    }
    
    throw new Error('No ROS2 communication method specified');
  }
}

4.3 Plugin Store Navigation

Location: HRIStudio - src/components/layout/navigation/SidebarNav.tsx

Add plugin store to the navigation:

const navigationItems = [
  {
    title: "Dashboard",
    href: "/",
    icon: LayoutDashboard,
    description: "Overview and quick actions"
  },
  {
    title: "Studies", 
    href: "/studies",
    icon: FolderOpen,
    description: "Research projects and team collaboration"
  },
  {
    title: "Experiments",
    href: "/experiments", 
    icon: FlaskConical,
    description: "Protocol design and validation"
  },
  {
    title: "Participants",
    href: "/participants",
    icon: Users,
    description: "Participant management and consent"
  },
  {
    title: "Trials",
    href: "/trials",
    icon: Play,
    description: "Experiment execution and monitoring"
  },
  // Add plugin store:
  {
    title: "Plugin Store",
    href: "/plugins",
    icon: Package,
    description: "Robot plugins and integrations"
  },
  {
    title: "Admin",
    href: "/admin",
    icon: Settings,
    description: "System administration",
    roles: ["administrator"]
  }
];

4.4 Plugin Configuration in Studies

Location: HRIStudio - src/app/(dashboard)/studies/[studyId]/settings/page.tsx

Add plugin configuration to study settings:

const StudySettingsPage = ({ studyId }: { studyId: string }) => {
  const installedPlugins = api.plugins.listInstalledPlugins.useQuery();
  const studyPlugins = api.plugins.getStudyPlugins.useQuery({ studyId });
  
  return (
    <PageLayout title="Study Settings">
      <Tabs defaultValue="general">
        <TabsList>
          <TabsTrigger value="general">General</TabsTrigger>
          <TabsTrigger value="team">Team</TabsTrigger>
          <TabsTrigger value="plugins">Robot Plugins</TabsTrigger>
          <TabsTrigger value="permissions">Permissions</TabsTrigger>
        </TabsList>
        
        <TabsContent value="plugins">
          <Card>
            <CardHeader>
              <CardTitle>Robot Plugins</CardTitle>
              <CardDescription>
                Configure which robot plugins are available for this study
              </CardDescription>
            </CardHeader>
            <CardContent>
              <PluginConfiguration
                studyId={studyId}
                availablePlugins={installedPlugins.data || []}
                enabledPlugins={studyPlugins.data || []}
              />
            </CardContent>
          </Card>
        </TabsContent>
      </Tabs>
    </PageLayout>
  );
};

Phase 5: Testing and Validation

5.1 Plugin Repository Testing

Create test scripts to validate:

  • Repository structure and schema compliance
  • Plugin definition validation
  • Web interface functionality
  • API endpoint responses

5.2 HRIStudio Integration Testing

Test the complete flow:

  1. Add plugin repository to HRIStudio
  2. Install a plugin from the repository
  3. Configure plugin for a study
  4. Use plugin actions in experiment designer
  5. Execute plugin actions during trial

5.3 End-to-End Testing

Create automated tests that:

  • Validate plugin installation process
  • Test ROS2 communication via rosbridge
  • Verify parameter validation and transformation
  • Test error handling and recovery

Deployment Checklist

Plugin Repository

  • Update plugin schema documentation
  • Enhance existing plugin definitions
  • Test web interface with new schema
  • Deploy to GitHub Pages or hosting platform
  • Validate HTTPS access and CORS headers

HRIStudio Platform

  • Implement database schema migrations
  • Create plugin store frontend pages
  • Implement plugin management tRPC routes
  • Integrate plugins with experiment designer
  • Add plugin execution to trial system
  • Update navigation to include plugin store
  • Add plugin configuration to study settings

Integration Testing

  • Test repository discovery and syncing
  • Validate plugin installation workflow
  • Test plugin action execution
  • Verify ROS2 integration works end-to-end
  • Test error handling and user feedback

This implementation will create a complete plugin ecosystem for HRIStudio, allowing researchers to easily discover, install, and use robot plugins in their studies.