We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sandraschi/robotics-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
import React, { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Plane,
Zap,
Map,
Navigation,
Camera,
Battery,
Activity,
Play,
Square,
RotateCcw,
Settings,
Home,
MapPin,
Target,
Wifi,
WifiOff,
Loader2,
CheckCircle,
XCircle
} from 'lucide-react'
import { DreameControls } from '@/components/DreameControls'
import { MapVisualization } from '@/components/MapVisualization'
import { mcpService } from '@/services/mcpService'
export default function FlyingCleaningControlPage() {
const [activeRobot, setActiveRobot] = useState<'dreame' | 'tdrone'>('dreame')
const [isConnected, setIsConnected] = useState(false)
const [robotData, setRobotData] = useState<any>(null)
const [mapData, setMapData] = useState<any>(null)
const [commandHistory, setCommandHistory] = useState<string[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
// Dreame robot configuration
const DREAME_ROBOT_ID = 'dreame_01'
// Load real Dreame robot data
useEffect(() => {
loadDreameData()
}, [])
const loadDreameData = async () => {
setIsLoading(true)
setError(null)
try {
// Load robot status
const statusResult = await mcpService.dreameStatus(DREAME_ROBOT_ID)
if (statusResult.success && statusResult.data) {
const robotInfo = statusResult.data
setRobotData({
robot_id: DREAME_ROBOT_ID,
robot_type: 'dreame',
name: 'Dreame D20 Pro',
connected: true,
battery: robotInfo.battery || 0,
position: robotInfo.position || { x: 0, y: 0, z: 0 },
rotation: robotInfo.rotation || { yaw: 0 },
status: robotInfo.status || 'idle',
map_available: true,
sensors: {
lidar: { status: 'active', range: 8.0 },
cliff: [true, true, true, true], // front sensors
}
})
setIsConnected(true)
// Try to load map data
try {
const mapResult = await mcpService.dreameMap(DREAME_ROBOT_ID)
if (mapResult.success && mapResult.data) {
setMapData({
map_id: `dreame_${DREAME_ROBOT_ID}_${Date.now()}`,
dreame_map: {
map_data: mapResult.data,
scale_factor: mapResult.data.scale_factor || 0.001,
origin_x: 0,
origin_y: 0
},
obstacles: [], // Will be populated from LIDAR data
rooms: [], // Will be populated from map segmentation
})
}
} catch (mapError) {
console.warn('Could not load Dreame map:', mapError)
}
} else {
// Fallback to mock data if real robot not available
console.warn('Dreame robot not available, using mock data')
setRobotData({
robot_id: DREAME_ROBOT_ID,
robot_type: 'dreame',
name: 'Dreame D20 Pro (Mock)',
connected: false,
battery: 0,
position: { x: 0, y: 0, z: 0 },
rotation: { yaw: 0 },
status: 'disconnected',
map_available: false,
sensors: {
lidar: { status: 'inactive', range: 0 },
cliff: [false, false, false, false],
}
})
setIsConnected(false)
setError('Dreame robot not connected. Please check Robotics MCP server.')
}
} catch (error) {
console.error('Failed to load Dreame data:', error)
setError(`Failed to load robot data: ${error instanceof Error ? error.message : String(error)}`)
setIsConnected(false)
} finally {
setIsLoading(false)
}
}
// Remove old mock loading logic
useEffect(() => {
// This is now handled by loadDreameData()
}, [activeRobot])
const handleRobotCommand = (robotId: string, command: any) => {
setCommandHistory(prev => [`${new Date().toLocaleTimeString()}: ${JSON.stringify(command)}`, ...prev.slice(0, 9)])
if (command.action === 'map_loaded' && command.map_data) {
setMapData(command.map_data)
}
}
const switchRobot = (robotType: 'dreame' | 'tdrone') => {
setActiveRobot(robotType)
loadDreameData() // Reload data when switching
}
const loadDreameMap = async (robotId: string) => {
try {
const result = await mcpService.dreameMap(robotId)
if (result.success && result.data) {
setMapData({
map_id: `dreame_${robotId}_${Date.now()}`,
dreame_map: {
map_data: result.data,
scale_factor: result.data.scale_factor || 0.001,
origin_x: 0,
origin_y: 0
},
obstacles: [],
rooms: [],
})
}
} catch (error) {
console.error('Failed to load Dreame map:', error)
setError('Failed to load LIDAR map')
}
}
// Show loading state
if (isLoading) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 flex items-center justify-center">
<div className="text-center">
<Loader2 className="w-12 h-12 animate-spin mx-auto mb-4 text-blue-600" />
<h2 className="text-xl font-semibold mb-2">Connecting to Dreame Robot</h2>
<p className="text-gray-600">Loading robot status and LIDAR map data...</p>
</div>
</div>
)
}
// Show error state
if (error) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-red-50 to-indigo-50 flex items-center justify-center">
<div className="text-center max-w-md">
<XCircle className="w-16 h-16 mx-auto mb-4 text-red-600" />
<h2 className="text-xl font-semibold mb-2 text-red-900">Connection Failed</h2>
<p className="text-red-700 mb-4">{error}</p>
<Button onClick={loadDreameData} variant="outline">
Retry Connection
</Button>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50">
<div className="container mx-auto px-4 py-8 max-w-7xl">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="p-3 bg-gradient-to-r from-green-100 to-blue-100 rounded-lg">
{activeRobot === 'dreame' ? (
<Zap className="h-8 w-8 text-green-600" />
) : (
<Plane className="h-8 w-8 text-blue-600" />
)}
</div>
<div>
<h1 className="text-3xl font-bold text-gray-900">
{activeRobot === 'dreame' ? 'Dreame D20 Pro' : 'Tdrone Mini'} Control
</h1>
<p className="text-gray-600">
{activeRobot === 'dreame'
? 'Advanced robotic vacuum with LIDAR mapping and zone cleaning'
: 'Affordable educational drone with PX4 flight controller'
}
</p>
</div>
</div>
<div className="flex items-center space-x-4">
{/* Robot Switcher */}
<Select value={activeRobot} onValueChange={(value: 'dreame' | 'tdrone') => switchRobot(value)}>
<SelectTrigger className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="dreame">
<div className="flex items-center space-x-2">
<Zap className="w-4 h-4" />
<span>Dreame D20 Pro</span>
</div>
</SelectItem>
<SelectItem value="tdrone">
<div className="flex items-center space-x-2">
<Plane className="w-4 h-4" />
<span>Tdrone Mini</span>
</div>
</SelectItem>
</SelectContent>
</Select>
<Badge variant={isConnected ? "default" : "destructive"} className="px-3 py-1">
{isConnected ? (
<>
<Wifi className="w-4 h-4 mr-2" />
Connected
</>
) : (
<>
<WifiOff className="w-4 h-4 mr-2" />
Connecting...
</>
)}
</Badge>
{robotData && (
<div className="flex items-center space-x-2 text-sm text-gray-600">
<Battery className="w-4 h-4" />
<span>{robotData.battery}%</span>
<Activity className="w-4 h-4 ml-4" />
<span>{robotData.status}</span>
</div>
)}
</div>
</div>
</div>
{!isConnected && (
<Alert className="mb-6 border-amber-200 bg-amber-50">
<Activity className="h-4 w-4 text-amber-600" />
<AlertDescription className="text-amber-800">
Connecting to {activeRobot === 'dreame' ? 'Dreame D20 Pro' : 'Tdrone Mini'}...
Please ensure the device is powered on and properly configured.
</AlertDescription>
</Alert>
)}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Main Control Panel */}
<div className="lg:col-span-2 space-y-6">
{/* Robot Status */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Activity className="h-5 w-5" />
<span>Robot Status</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="text-center">
<div className="text-2xl font-bold text-blue-600">{robotData?.battery || 0}%</div>
<div className="text-sm text-gray-600">Battery</div>
<div className="w-full bg-gray-200 rounded-full h-2 mt-2">
<div
className="bg-blue-600 h-2 rounded-full"
style={{ width: `${robotData?.battery || 0}%` }}
></div>
</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-green-600">
{robotData?.position ? `${robotData.position.x.toFixed(1)}, ${robotData.position.y.toFixed(1)}` : '0.0, 0.0'}
</div>
<div className="text-sm text-gray-600">Position (m)</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">{robotData?.rotation?.yaw?.toFixed(0) || 0}°</div>
<div className="text-sm text-gray-600">Heading</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-orange-600">{robotData?.status || 'offline'}</div>
<div className="text-sm text-gray-600">Status</div>
</div>
</div>
</CardContent>
</Card>
{/* Robot-Specific Controls */}
{activeRobot === 'dreame' && robotData && (
<DreameControls
robot={{
robot_id: robotData.robot_id,
robot_type: robotData.robot_type,
connected: robotData.connected,
battery: robotData.battery,
map_available: robotData.map_available,
status: robotData.status
}}
onCommand={handleRobotCommand}
/>
)}
{activeRobot === 'tdrone' && robotData && (
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Plane className="h-5 w-5" />
<span>Tdrone Flight Controls</span>
</CardTitle>
<CardDescription>PX4-powered quadcopter with autonomous flight capabilities</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="manual" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="manual">Manual</TabsTrigger>
<TabsTrigger value="auto">Auto</TabsTrigger>
<TabsTrigger value="camera">Camera</TabsTrigger>
<TabsTrigger value="telemetry">Telemetry</TabsTrigger>
</TabsList>
<TabsContent value="manual" className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<Button onClick={() => handleRobotCommand('tdrone_01', { action: 'takeoff', altitude: 3.0 })}>
<Play className="w-4 h-4 mr-2" />
Take Off
</Button>
<Button variant="destructive" onClick={() => handleRobotCommand('tdrone_01', { action: 'land' })}>
<Square className="w-4 h-4 mr-2" />
Land
</Button>
<Button variant="outline" onClick={() => handleRobotCommand('tdrone_01', { action: 'rtl' })}>
<Home className="w-4 h-4 mr-2" />
Return Home
</Button>
<Button variant="outline" onClick={() => handleRobotCommand('tdrone_01', { action: 'emergency_stop' })}>
<Square className="w-4 h-4 mr-2" />
Emergency Stop
</Button>
</div>
<div className="space-y-2">
<Label>Altitude Control</Label>
<div className="flex space-x-2">
<Button size="sm" onClick={() => handleRobotCommand('tdrone_01', { action: 'move', linear_z: 0.5 })}>
↑ Up
</Button>
<Button size="sm" onClick={() => handleRobotCommand('tdrone_01', { action: 'move', linear_z: -0.5 })}>
↓ Down
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="auto" className="space-y-4">
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="waypoint-x">Waypoint X</Label>
<Input id="waypoint-x" type="number" step="0.1" placeholder="0.0" />
</div>
<div className="space-y-2">
<Label htmlFor="waypoint-y">Waypoint Y</Label>
<Input id="waypoint-y" type="number" step="0.1" placeholder="0.0" />
</div>
</div>
<Button onClick={() => {
const x = parseFloat((document.getElementById('waypoint-x') as HTMLInputElement)?.value || '0')
const y = parseFloat((document.getElementById('waypoint-y') as HTMLInputElement)?.value || '0')
handleRobotCommand('tdrone_01', { action: 'goto_waypoint', x, y, altitude: 5.0 })
}}>
<Navigation className="w-4 h-4 mr-2" />
Go to Waypoint
</Button>
</div>
<div className="space-y-2">
<Label>Flight Modes</Label>
<div className="grid grid-cols-2 gap-2">
<Button variant="outline" size="sm" onClick={() => handleRobotCommand('tdrone_01', { action: 'set_mode', mode: 'stabilize' })}>
Stabilize
</Button>
<Button variant="outline" size="sm" onClick={() => handleRobotCommand('tdrone_01', { action: 'set_mode', mode: 'loiter' })}>
Loiter
</Button>
<Button variant="outline" size="sm" onClick={() => handleRobotCommand('tdrone_01', { action: 'set_mode', mode: 'auto' })}>
Auto
</Button>
<Button variant="outline" size="sm" onClick={() => handleRobotCommand('tdrone_01', { action: 'set_mode', mode: 'rtl' })}>
RTL
</Button>
</div>
</div>
</TabsContent>
<TabsContent value="camera" className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Camera Status:</span>
<Badge variant={robotData?.sensors?.camera?.streaming ? "default" : "secondary"}>
{robotData?.sensors?.camera?.streaming ? 'Streaming' : 'Offline'}
</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Resolution:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.camera?.resolution || 'N/A'}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">FPS:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.camera?.fps || 0}</span>
</div>
</div>
<div className="flex space-x-2">
<Button onClick={() => handleRobotCommand('tdrone_01', { action: 'start_recording' })}>
<Play className="w-4 h-4 mr-2" />
Record
</Button>
<Button variant="outline" onClick={() => handleRobotCommand('tdrone_01', { action: 'take_snapshot' })}>
<Camera className="w-4 h-4 mr-2" />
Snapshot
</Button>
</div>
</TabsContent>
<TabsContent value="telemetry" className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">GPS Satellites:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.gps?.satellites || 0}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">GPS Accuracy:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.gps?.accuracy?.toFixed(1) || 0}m</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Optical Flow:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.optical_flow?.quality || 0}%</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Flight Time:</span>
<span className="text-sm text-gray-600">{Math.floor((robotData?.flight?.flight_time || 0) / 60)}:{((robotData?.flight?.flight_time || 0) % 60).toString().padStart(2, '0')}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Altitude:</span>
<span className="text-sm text-gray-600">{robotData?.position?.z?.toFixed(1) || 0}m</span>
</div>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
)}
{/* Sensor Data */}
<Card>
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Activity className="h-5 w-5" />
<span>Sensor Data</span>
</CardTitle>
</CardHeader>
<CardContent>
{activeRobot === 'dreame' ? (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">LiDAR Status:</span>
<Badge variant="default">Active</Badge>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Range:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.lidar?.range || 0}m</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Area Cleaned:</span>
<span className="text-sm text-gray-600">{robotData?.cleaning?.area?.toFixed(1) || 0} m²</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Cleaning Time:</span>
<span className="text-sm text-gray-600">{Math.floor((robotData?.cleaning?.time || 0) / 60)}:{((robotData?.cleaning?.time || 0) % 60).toString().padStart(2, '0')}</span>
</div>
</div>
) : (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">GPS Satellites:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.gps?.satellites || 0}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Optical Flow:</span>
<span className="text-sm text-gray-600">{robotData?.sensors?.optical_flow?.quality || 0}%</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Altitude:</span>
<span className="text-sm text-gray-600">{robotData?.position?.z?.toFixed(1) || 0}m</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Flight Mode:</span>
<Badge variant="outline">{robotData?.flight?.mode || 'unknown'}</Badge>
</div>
</div>
)}
</CardContent>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Device Info */}
<Card>
<CardHeader>
<CardTitle>Device Information</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span>Name:</span>
<span className="font-medium">{robotData?.name || 'Unknown'}</span>
</div>
<div className="flex justify-between">
<span>ID:</span>
<span className="font-mono text-xs">{robotData?.robot_id || 'N/A'}</span>
</div>
<div className="flex justify-between">
<span>Type:</span>
<Badge variant="outline" className="text-xs">{robotData?.robot_type || 'unknown'}</Badge>
</div>
{activeRobot === 'dreame' && (
<>
<div className="flex justify-between">
<span>Suction:</span>
<span>{robotData?.cleaning?.suction || 'N/A'}</span>
</div>
<div className="flex justify-between">
<span>Cleaning Mode:</span>
<span className="capitalize">{robotData?.cleaning?.mode || 'N/A'}</span>
</div>
</>
)}
{activeRobot === 'tdrone' && (
<>
<div className="flex justify-between">
<span>Flight Controller:</span>
<span>PX4/ArduPilot</span>
</div>
<div className="flex justify-between">
<span>Max Altitude:</span>
<span>120m</span>
</div>
</>
)}
</div>
</CardContent>
</Card>
{/* Quick Actions */}
<Card>
<CardHeader>
<CardTitle>Quick Actions</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{activeRobot === 'dreame' ? (
<>
<Button className="w-full" onClick={() => handleRobotCommand('dreame_01', { action: 'start_cleaning' })}>
<Play className="w-4 h-4 mr-2" />
Start Cleaning
</Button>
<Button variant="outline" className="w-full" onClick={() => handleRobotCommand('dreame_01', { action: 'return_to_dock' })}>
<Home className="w-4 h-4 mr-2" />
Return to Dock
</Button>
<Button variant="outline" className="w-full" onClick={() => handleRobotCommand('dreame_01', { action: 'get_map' })}>
<Map className="w-4 h-4 mr-2" />
Update Map
</Button>
</>
) : (
<>
<Button className="w-full" onClick={() => handleRobotCommand('tdrone_01', { action: 'takeoff', altitude: 3.0 })}>
<Plane className="w-4 h-4 mr-2" />
Take Off
</Button>
<Button variant="outline" className="w-full" onClick={() => handleRobotCommand('tdrone_01', { action: 'rtl' })}>
<Home className="w-4 h-4 mr-2" />
Return Home
</Button>
<Button variant="outline" className="w-full" onClick={() => handleRobotCommand('tdrone_01', { action: 'start_recording' })}>
<Camera className="w-4 h-4 mr-2" />
Start Recording
</Button>
</>
)}
</CardContent>
</Card>
{/* Command History */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Command History</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2 max-h-48 overflow-y-auto">
{commandHistory.length === 0 ? (
<p className="text-sm text-gray-500 text-center py-4">No commands sent yet</p>
) : (
commandHistory.map((command, index) => (
<div key={index} className="text-xs font-mono bg-gray-100 p-2 rounded">
{command}
</div>
))
)}
</div>
</CardContent>
</Card>
</div>
</div>
{/* Map Visualization */}
<Card className="mt-8">
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<Map className="h-5 w-5" />
<span>Navigation & Mapping</span>
</CardTitle>
<CardDescription>
{activeRobot === 'dreame'
? 'Real-time LIDAR mapping and cleaning path visualization'
: 'GPS waypoint navigation and flight path tracking'
}
</CardDescription>
</CardHeader>
<CardContent>
<MapVisualization
robots={robotData ? [robotData] : []}
mapData={activeRobot === 'dreame' ? mapData : undefined}
onRobotCommand={handleRobotCommand}
onLoadDreameMap={loadDreameMap}
className="w-full"
/>
</CardContent>
</Card>
</div>
</div>
)
}