leLab / src /hooks /useRealTimeJoints.ts
jurmy24's picture
mega big updates pre-demo
9a9d18a
import { useEffect, useRef, useCallback } from "react";
import { URDFViewerElement } from "@/lib/urdfViewerHelpers";
import { useApi } from "@/contexts/ApiContext";
interface JointData {
type: "joint_update";
joints: Record<string, number>;
timestamp: number;
}
interface UseRealTimeJointsProps {
viewerRef: React.RefObject<URDFViewerElement>;
enabled?: boolean;
websocketUrl?: string;
}
export const useRealTimeJoints = ({
viewerRef,
enabled = true,
websocketUrl,
}: UseRealTimeJointsProps) => {
const { baseUrl, wsBaseUrl, fetchWithHeaders } = useApi();
const defaultWebSocketUrl = `${wsBaseUrl}/ws/joint-data`;
const finalWebSocketUrl = websocketUrl || defaultWebSocketUrl;
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const isConnectedRef = useRef<boolean>(false);
const updateJointValues = useCallback(
(joints: Record<string, number>) => {
const viewer = viewerRef.current;
if (!viewer || typeof viewer.setJointValue !== "function") {
return;
}
// Update each joint value in the URDF viewer
Object.entries(joints).forEach(([jointName, value]) => {
try {
viewer.setJointValue(jointName, value);
} catch (error) {
console.warn(`Failed to set joint ${jointName}:`, error);
}
});
},
[viewerRef]
);
const connectWebSocket = useCallback(() => {
if (!enabled) return;
// First, test if the server is running
const testServerConnection = async () => {
try {
const response = await fetchWithHeaders(`${baseUrl}/health`);
if (!response.ok) {
console.error("❌ Server health check failed:", response.status);
return false;
}
const data = await response.json();
console.log("✅ Server is running:", data);
return true;
} catch (error) {
console.error("❌ Server is not reachable:", error);
return false;
}
};
// Test server connection first
testServerConnection().then((serverAvailable) => {
if (!serverAvailable) {
console.error("❌ Cannot connect to WebSocket: Server is not running");
console.log(
"💡 Make sure to start the FastAPI server with: python -m uvicorn lerobot.livelab.app.main:app --reload"
);
return;
}
try {
console.log("🔗 Connecting to WebSocket:", finalWebSocketUrl);
const ws = new WebSocket(finalWebSocketUrl);
wsRef.current = ws;
ws.onopen = () => {
console.log("✅ WebSocket connected for real-time joints");
isConnectedRef.current = true;
// Clear any existing reconnect timeout
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
};
ws.onmessage = (event) => {
try {
const data: JointData = JSON.parse(event.data);
if (data.type === "joint_update" && data.joints) {
updateJointValues(data.joints);
}
} catch (error) {
console.error("❌ Error parsing WebSocket message:", error);
}
};
ws.onclose = (event) => {
console.log(
"🔌 WebSocket connection closed:",
event.code,
event.reason
);
isConnectedRef.current = false;
wsRef.current = null;
// Provide more specific error information
if (event.code === 1006) {
console.error(
"❌ WebSocket connection failed - server may not be running or endpoint not found"
);
} else if (event.code === 1000) {
console.log("✅ WebSocket closed normally");
}
// Attempt to reconnect after a delay if enabled
if (enabled && !reconnectTimeoutRef.current && event.code !== 1000) {
reconnectTimeoutRef.current = setTimeout(() => {
console.log("🔄 Attempting to reconnect WebSocket...");
connectWebSocket();
}, 3000); // Reconnect after 3 seconds
}
};
ws.onerror = (error) => {
console.error("❌ WebSocket error:", error);
console.log("💡 Troubleshooting tips:");
console.log(
" 1. Make sure FastAPI server is running on localhost:8000"
);
console.log(" 2. Check if the /ws/joint-data endpoint exists");
console.log(
" 3. Restart the server if you just added WebSocket support"
);
isConnectedRef.current = false;
};
} catch (error) {
console.error("❌ Failed to create WebSocket connection:", error);
}
});
}, [enabled, websocketUrl, updateJointValues]);
const disconnect = useCallback(() => {
// Clear reconnect timeout
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
// Close WebSocket connection
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
isConnectedRef.current = false;
}, []);
// Effect to manage WebSocket connection
useEffect(() => {
if (enabled) {
connectWebSocket();
} else {
disconnect();
}
// Cleanup on unmount
return () => {
disconnect();
};
}, [enabled, connectWebSocket, disconnect]);
// Return connection status and control functions
return {
isConnected: isConnectedRef.current,
disconnect,
reconnect: connectWebSocket,
};
};