'use client'
import React, { useState } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Slider } from '@/components/ui/slider'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Alert, AlertDescription } from '@/components/ui/alert'
import {
Move,
RotateCcw,
Square,
Zap,
Eye,
Home,
Navigation,
Arm,
Hand,
Battery,
Thermometer,
Activity,
Settings
} from 'lucide-react'
interface YahboomRobot {
robot_id: string
robot_type: 'yahboom'
platform: 'raspbot_v2' | 'raspbot_v3'
connected: boolean
is_virtual?: boolean
battery?: {
voltage: number
percentage: number
charging: boolean
temperature: number
}
position?: {
x: number
y: number
theta: number
accuracy: number
}
sensors?: {
camera: boolean
imu: boolean
lidar: boolean
ultrasonic: boolean
}
capabilities?: {
navigation: boolean
arm: boolean
camera_streaming: boolean
patrol: boolean
}
ros_status?: {
master_connected: boolean
topics_count: number
nodes_count: number
}
}
interface YahboomControlsProps {
robot: YahboomRobot
onCommand: (robotId: string, command: any) => void
}
export function YahboomControls({ robot, onCommand }: YahboomControlsProps) {
const [linearVelocity, setLinearVelocity] = useState([0.3])
const [angularVelocity, setAngularVelocity] = useState([0])
const [armJointAngles, setArmJointAngles] = useState([0, 0, 0, 0, 0, 0])
const [gripperPosition, setGripperPosition] = useState([0])
const [patrolRoute, setPatrolRoute] = useState('home_perimeter')
const [cameraSettings, setCameraSettings] = useState({
resolution: '640x480',
fps: 30,
quality: 80
})
const handleMovementCommand = (direction: string) => {
let linear = 0
let angular = 0
switch (direction) {
case 'forward':
linear = linearVelocity[0]
break
case 'backward':
linear = -linearVelocity[0]
break
case 'left':
angular = angularVelocity[0]
break
case 'right':
angular = -angularVelocity[0]
break
case 'stop':
linear = 0
angular = 0
break
}
onCommand(robot.robot_id, {
action: 'move',
linear_x: linear,
linear_y: 0,
angular_z: angular
})
}
const handleArmCommand = (action: string) => {
onCommand(robot.robot_id, {
action: 'arm_move',
joint_angles: armJointAngles,
...(action === 'home' && { home: true })
})
}
const handleGripperCommand = (action: string) => {
onCommand(robot.robot_id, {
action: 'gripper_control',
gripper_action: action,
position: gripperPosition[0]
})
}
const handlePatrolCommand = (action: string) => {
onCommand(robot.robot_id, {
action: action === 'start' ? 'home_patrol' : 'stop_patrol',
route: patrolRoute
})
}
const handleNavigationCommand = (waypoint: string) => {
onCommand(robot.robot_id, {
action: 'navigate_to',
waypoint: waypoint
})
}
const handleCameraCommand = (action: string) => {
onCommand(robot.robot_id, {
action: action,
settings: cameraSettings
})
}
if (!robot.connected) {
return (
<Card className="w-full">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="w-5 h-5 text-orange-500" />
Yahboom ROSMASTER Controls
</CardTitle>
<CardDescription>Yahboom ROSMASTER (M1/X3/X3 Plus) Advanced Control Panel</CardDescription>
</CardHeader>
<CardContent>
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Robot is offline. Please check connection and ROS 2 status.
</AlertDescription>
</Alert>
</CardContent>
</Card>
)
}
return (
<Card className="w-full">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="w-5 h-5 text-blue-500" />
Yahboom ROSMASTER Controls
</CardTitle>
<CardDescription>Yahboom ROSMASTER (M1/X3/X3 Plus) Advanced Control Panel</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="movement" className="w-full">
<TabsList className="grid w-full grid-cols-5">
<TabsTrigger value="movement">Movement</TabsTrigger>
<TabsTrigger value="arm">Arm</TabsTrigger>
<TabsTrigger value="patrol">Patrol</TabsTrigger>
<TabsTrigger value="camera">Camera</TabsTrigger>
<TabsTrigger value="status">Status</TabsTrigger>
</TabsList>
<TabsContent value="movement" className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium mb-2 block">
Linear Velocity: {linearVelocity[0].toFixed(2)} m/s
</label>
<Slider
value={linearVelocity}
onValueChange={setLinearVelocity}
max={1.0}
min={-1.0}
step={0.1}
className="w-full"
/>
</div>
<div>
<label className="text-sm font-medium mb-2 block">
Angular Velocity: {angularVelocity[0].toFixed(2)} rad/s
</label>
<Slider
value={angularVelocity}
onValueChange={setAngularVelocity}
max={2.0}
min={-2.0}
step={0.1}
className="w-full"
/>
</div>
</div>
<div className="grid grid-cols-3 gap-2">
<Button
onClick={() => handleMovementCommand('forward')}
className="flex items-center gap-2"
variant="outline"
>
<Move className="w-4 h-4" />
Forward
</Button>
<Button
onClick={() => handleMovementCommand('left')}
className="flex items-center gap-2"
variant="outline"
>
<RotateCcw className="w-4 h-4" />
Left
</Button>
<Button
onClick={() => handleMovementCommand('right')}
className="flex items-center gap-2"
variant="outline"
>
<RotateCcw className="w-4 h-4 rotate-180" />
Right
</Button>
<Button
onClick={() => handleMovementCommand('stop')}
className="flex items-center gap-2"
variant="destructive"
>
<Square className="w-4 h-4" />
Stop
</Button>
<Button
onClick={() => handleMovementCommand('backward')}
className="flex items-center gap-2"
variant="outline"
>
<Move className="w-4 h-4 rotate-180" />
Backward
</Button>
<Button
onClick={() => handleNavigationCommand('home')}
className="flex items-center gap-2"
variant="outline"
>
<Home className="w-4 h-4" />
Go Home
</Button>
</div>
</TabsContent>
<TabsContent value="arm" className="space-y-4">
{robot.capabilities?.arm ? (
<>
<div className="space-y-3">
<h4 className="text-sm font-medium">Joint Control</h4>
{['Base', 'Shoulder', 'Elbow', 'Wrist Roll', 'Wrist Pitch', 'Gripper'].map((joint, index) => (
<div key={joint}>
<label className="text-xs text-gray-600 mb-1 block">
{joint}: {armJointAngles[index]?.toFixed(2)}°
</label>
<Slider
value={[armJointAngles[index]]}
onValueChange={(value) => {
const newAngles = [...armJointAngles]
newAngles[index] = value[0]
setArmJointAngles(newAngles)
}}
max={180}
min={-180}
step={1}
className="w-full"
/>
</div>
))}
</div>
<div className="flex gap-2">
<Button
onClick={() => handleArmCommand('move')}
className="flex items-center gap-2"
>
<Arm className="w-4 h-4" />
Move Arm
</Button>
<Button
onClick={() => handleArmCommand('home')}
variant="outline"
className="flex items-center gap-2"
>
<Home className="w-4 h-4" />
Home Position
</Button>
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Gripper Control</h4>
<label className="text-xs text-gray-600 mb-1 block">
Position: {gripperPosition[0]} (0=open, 100=closed)
</label>
<Slider
value={gripperPosition}
onValueChange={setGripperPosition}
max={100}
min={0}
step={1}
className="w-full"
/>
<div className="flex gap-2">
<Button
onClick={() => handleGripperCommand('open')}
variant="outline"
size="sm"
>
<Hand className="w-4 h-4" />
Open
</Button>
<Button
onClick={() => handleGripperCommand('close')}
variant="outline"
size="sm"
>
<Hand className="w-4 h-4" />
Close
</Button>
<Button
onClick={() => handleGripperCommand('stop')}
variant="outline"
size="sm"
>
<Square className="w-4 h-4" />
Stop
</Button>
</div>
</div>
</>
) : (
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Arm addon not detected. Please attach the Yahboom robotic arm for full arm control.
</AlertDescription>
</Alert>
)}
</TabsContent>
<TabsContent value="patrol" className="space-y-4">
<div>
<label className="text-sm font-medium mb-2 block">Patrol Route</label>
<select
value={patrolRoute}
onChange={(e) => setPatrolRoute(e.target.value)}
className="w-full p-2 border rounded"
>
<option value="home_perimeter">Home Perimeter</option>
<option value="living_room">Living Room</option>
<option value="kitchen">Kitchen</option>
<option value="bedroom">Bedroom</option>
<option value="custom">Custom Route</option>
</select>
</div>
<div className="flex gap-2">
<Button
onClick={() => handlePatrolCommand('start')}
className="flex items-center gap-2"
>
<Navigation className="w-4 h-4" />
Start Patrol
</Button>
<Button
onClick={() => handlePatrolCommand('stop')}
variant="destructive"
className="flex items-center gap-2"
>
<Square className="w-4 h-4" />
Stop Patrol
</Button>
</div>
<Alert>
<Activity className="h-4 w-4" />
<AlertDescription>
Patrol mode enables autonomous navigation with obstacle avoidance and motion detection.
The robot will follow the selected route and return alerts for detected movement.
</AlertDescription>
</Alert>
</TabsContent>
<TabsContent value="camera" className="space-y-4">
{robot.capabilities?.camera_streaming ? (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium mb-2 block">Resolution</label>
<select
value={cameraSettings.resolution}
onChange={(e) => setCameraSettings(prev => ({ ...prev, resolution: e.target.value }))}
className="w-full p-2 border rounded"
>
<option value="320x240">320x240</option>
<option value="640x480">640x480</option>
<option value="1280x720">1280x720</option>
<option value="1920x1080">1920x1080</option>
</select>
</div>
<div>
<label className="text-sm font-medium mb-2 block">FPS: {cameraSettings.fps}</label>
<Slider
value={[cameraSettings.fps]}
onValueChange={(value) => setCameraSettings(prev => ({ ...prev, fps: value[0] }))}
max={60}
min={1}
step={1}
className="w-full"
/>
</div>
</div>
<div className="flex gap-2">
<Button
onClick={() => handleCameraCommand('get_camera')}
className="flex items-center gap-2"
>
<Eye className="w-4 h-4" />
View Stream
</Button>
<Button
onClick={() => handleCameraCommand('camera_capture')}
variant="outline"
className="flex items-center gap-2"
>
<Camera className="w-4 h-4" />
Capture Image
</Button>
</div>
</>
) : (
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Camera not available. Please check camera connection and ROS 2 camera node status.
</AlertDescription>
</Alert>
)}
</TabsContent>
<TabsContent value="status" className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm flex items-center gap-2">
<Battery className="w-4 h-4" />
Battery
</CardTitle>
</CardHeader>
<CardContent>
{robot.battery ? (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span>Voltage:</span>
<span>{robot.battery.voltage.toFixed(2)}V</span>
</div>
<div className="flex justify-between text-sm">
<span>Level:</span>
<span>{robot.battery.percentage}%</span>
</div>
<div className="flex justify-between text-sm">
<span>Status:</span>
<Badge variant={robot.battery.charging ? "default" : "secondary"}>
{robot.battery.charging ? "Charging" : "Discharging"}
</Badge>
</div>
<div className="flex justify-between text-sm">
<span>Temp:</span>
<span>{robot.battery.temperature}°C</span>
</div>
</div>
) : (
<p className="text-sm text-gray-500">No battery data</p>
)}
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm flex items-center gap-2">
<Navigation className="w-4 h-4" />
Position
</CardTitle>
</CardHeader>
<CardContent>
{robot.position ? (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span>X:</span>
<span>{robot.position.x.toFixed(2)}m</span>
</div>
<div className="flex justify-between text-sm">
<span>Y:</span>
<span>{robot.position.y.toFixed(2)}m</span>
</div>
<div className="flex justify-between text-sm">
<span>Heading:</span>
<span>{(robot.position.theta * 180 / Math.PI).toFixed(1)}°</span>
</div>
<div className="flex justify-between text-sm">
<span>Accuracy:</span>
<span>{robot.position.accuracy.toFixed(2)}m</span>
</div>
</div>
) : (
<p className="text-sm text-gray-500">No position data</p>
)}
</CardContent>
</Card>
</div>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm flex items-center gap-2">
<Settings className="w-4 h-4" />
ROS 2 Status
</CardTitle>
</CardHeader>
<CardContent>
{robot.ros_status ? (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span>ROS Master:</span>
<Badge variant={robot.ros_status.master_connected ? "default" : "destructive"}>
{robot.ros_status.master_connected ? "Connected" : "Disconnected"}
</Badge>
</div>
<div className="flex justify-between text-sm">
<span>Topics:</span>
<span>{robot.ros_status.topics_count}</span>
</div>
<div className="flex justify-between text-sm">
<span>Nodes:</span>
<span>{robot.ros_status.nodes_count}</span>
</div>
</div>
) : (
<p className="text-sm text-gray-500">No ROS data available</p>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</CardContent>
</Card>
)
}