'use client'; import { useState, useEffect, useRef } from 'react'; export default function RobotDashboard() { // Sesuaikan dengan IP Raspberry Pi / Tailscale milikmu const PI_IP = '172.17.19.107'; const streamUrl = `http://${PI_IP}:8080/stream`; // Tipe data disederhanakan agar tidak memicu error TS const [controlMode, setControlMode] = useState('gamepad'); const [gamepadStatus, setGamepadStatus] = useState("Menunggu Gamepad..."); const [steering, setSteering] = useState({ x: 0, y: 0 }); // Menggunakan 'any' untuk menghindari error pengecekan tipe strict pada WebSocket const wsRef = useRef(null); const keysRef = useRef({ up: false, down: false, left: false, right: false }); const forceScanGamepad = () => { if (typeof navigator !== "undefined" && navigator.getGamepads) { const gamepads = navigator.getGamepads(); let activeGp = null; for (let i = 0; i < gamepads.length; i++) { if (gamepads[i] !== null) { activeGp = gamepads[i]; break; } } if (activeGp) setGamepadStatus(`🟢 TERHUBUNG: ${activeGp.id.substring(0, 15)}...`); } }; useEffect(() => { const ws = new WebSocket(`ws://${PI_IP}:8765`); ws.onopen = () => console.log("WebSocket Terhubung ke Pi!"); ws.onclose = () => console.log("WebSocket Terputus."); wsRef.current = ws; const handleKeyDown = (e: KeyboardEvent) => { if (['ArrowUp', 'w', 'W'].includes(e.key)) keysRef.current.up = true; if (['ArrowDown', 's', 'S'].includes(e.key)) keysRef.current.down = true; if (['ArrowLeft', 'a', 'A'].includes(e.key)) keysRef.current.left = true; if (['ArrowRight', 'd', 'D'].includes(e.key)) keysRef.current.right = true; }; const handleKeyUp = (e: KeyboardEvent) => { if (['ArrowUp', 'w', 'W'].includes(e.key)) keysRef.current.up = false; if (['ArrowDown', 's', 'S'].includes(e.key)) keysRef.current.down = false; if (['ArrowLeft', 'a', 'A'].includes(e.key)) keysRef.current.left = false; if (['ArrowRight', 'd', 'D'].includes(e.key)) keysRef.current.right = false; }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); const interval = setInterval(() => { let currentX = 0; let currentY = 0; if (controlMode === 'gamepad') { if (typeof navigator !== "undefined" && navigator.getGamepads) { const gamepads = navigator.getGamepads(); let activeGp = null; for (let i = 0; i < gamepads.length; i++) { if (gamepads[i] !== null) { activeGp = gamepads[i]; break; } } if (activeGp) { const rawX = activeGp.axes[0]; const lt = activeGp.buttons[6].value; const rt = activeGp.buttons[7].value; const throttle = rt - lt; currentX = Math.abs(rawX) > 0.15 ? Number(rawX.toFixed(2)) : 0; currentY = Number(throttle.toFixed(2)); } } } else if (controlMode === 'keyboard') { currentY = (keysRef.current.up ? 1 : 0) - (keysRef.current.down ? 1 : 0); currentX = (keysRef.current.right ? 1 : 0) - (keysRef.current.left ? 1 : 0); } setSteering({ x: currentX, y: currentY }); // wsRef dipastikan aman dari TS error if (wsRef.current && wsRef.current.readyState === 1) { wsRef.current.send(JSON.stringify({ x: currentX, y: currentY })); } }, 100); return () => { clearInterval(interval); window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); if (wsRef.current) wsRef.current.close(); }; }, [controlMode, PI_IP]); return (

Robot Vision Control

Live Feed

{/* eslint-disable-next-line @next/next/no-img-element */} Robot view

Status Kontrol {controlMode === 'gamepad' && ( )}

{controlMode === 'keyboard' ? '⌨️ AKTIF: Gunakan W-A-S-D atau Panah' : gamepadStatus}
Visualizer (X / Y)
X: {steering.x} Y: {steering.y}
); }