cduss's picture
simpler
52fb363
// Reachy Mini Control Panel - WebSocket Version
// Connects to localhost:8000 WebSocket API for real-time control
const ROBOT_URL = 'localhost:8000';
const WS_URL = `ws://${ROBOT_URL}/api/move/ws/set_target`;
// Global state
const state = {
ws: null,
connected: false,
isRefreshing: false,
currentPose: {
head: { x: 0, y: 0, z: 0, roll: 0, pitch: 0, yaw: 0 },
bodyYaw: 0,
antennas: [0, 0]
}
};
// DOM elements
const elements = {
status: document.getElementById('connectionStatus'),
sliders: {
headX: document.getElementById('headX'),
headY: document.getElementById('headY'),
headZ: document.getElementById('headZ'),
headRoll: document.getElementById('headRoll'),
headPitch: document.getElementById('headPitch'),
headYaw: document.getElementById('headYaw'),
bodyYaw: document.getElementById('bodyYaw'),
antennaLeft: document.getElementById('antennaLeft'),
antennaRight: document.getElementById('antennaRight')
},
values: {
headX: document.getElementById('headXValue'),
headY: document.getElementById('headYValue'),
headZ: document.getElementById('headZValue'),
headRoll: document.getElementById('headRollValue'),
headPitch: document.getElementById('headPitchValue'),
headYaw: document.getElementById('headYawValue'),
bodyYaw: document.getElementById('bodyYawValue'),
antennaLeft: document.getElementById('antennaLeftValue'),
antennaRight: document.getElementById('antennaRightValue')
}
};
// WebSocket connection
function connectWebSocket() {
console.log('Connecting to WebSocket:', WS_URL);
state.ws = new WebSocket(WS_URL);
state.ws.onopen = () => {
console.log('WebSocket connected');
state.connected = true;
updateConnectionStatus(true);
enableControls(true);
};
state.ws.onclose = () => {
console.log('WebSocket disconnected');
state.connected = false;
updateConnectionStatus(false);
enableControls(false);
// Reconnect after 2 seconds
setTimeout(connectWebSocket, 2000);
};
state.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
state.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
if (message.status === 'error') {
console.error('Server error:', message.detail);
}
} catch (e) {
console.error('Failed to parse message:', e);
}
};
}
// Update connection status UI
function updateConnectionStatus(connected) {
if (connected) {
elements.status.className = 'status connected';
elements.status.innerHTML = '<span><span class="status-dot green"></span>Connected to robot</span>';
} else {
elements.status.className = 'status disconnected';
elements.status.innerHTML = '<span><span class="status-dot red"></span>Disconnected - Reconnecting...</span>';
}
}
// Enable/disable controls
function enableControls(enabled) {
Object.values(elements.sliders).forEach(slider => {
slider.disabled = !enabled;
});
}
// Send target pose via WebSocket
function sendTargetPose() {
if (!state.connected || !state.ws || state.ws.readyState !== WebSocket.OPEN) {
console.warn('WebSocket not connected');
return;
}
if (state.isRefreshing) {
return; // Don't send during refresh
}
const message = {
target_head_pose: {
x: state.currentPose.head.x,
y: state.currentPose.head.y,
z: state.currentPose.head.z,
roll: state.currentPose.head.roll,
pitch: state.currentPose.head.pitch,
yaw: state.currentPose.head.yaw
},
target_body_yaw: state.currentPose.bodyYaw,
target_antennas: state.currentPose.antennas
};
try {
state.ws.send(JSON.stringify(message));
} catch (error) {
console.error('Failed to send message:', error);
}
}
// Slider change handlers
function setupSliderHandlers() {
// Head pose sliders
elements.sliders.headX.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.head.x = value;
elements.values.headX.textContent = value.toFixed(3);
sendTargetPose();
});
elements.sliders.headY.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.head.y = value;
elements.values.headY.textContent = value.toFixed(3);
sendTargetPose();
});
elements.sliders.headZ.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.head.z = value;
elements.values.headZ.textContent = value.toFixed(3);
sendTargetPose();
});
elements.sliders.headRoll.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.head.roll = value;
elements.values.headRoll.textContent = value.toFixed(2);
sendTargetPose();
});
elements.sliders.headPitch.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.head.pitch = value;
elements.values.headPitch.textContent = value.toFixed(2);
sendTargetPose();
});
elements.sliders.headYaw.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.head.yaw = value;
elements.values.headYaw.textContent = value.toFixed(2);
sendTargetPose();
});
// Body yaw slider
elements.sliders.bodyYaw.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.bodyYaw = value;
elements.values.bodyYaw.textContent = value.toFixed(2);
sendTargetPose();
});
// Antenna sliders
elements.sliders.antennaLeft.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.antennas[0] = value;
elements.values.antennaLeft.textContent = value.toFixed(2);
sendTargetPose();
});
elements.sliders.antennaRight.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
state.currentPose.antennas[1] = value;
elements.values.antennaRight.textContent = value.toFixed(2);
sendTargetPose();
});
}
// Initialize app
function init() {
console.log('Initializing Reachy Mini Control Panel');
setupSliderHandlers();
connectWebSocket();
console.log('Control panel ready');
}
// Start when DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}