cduss's picture
wip
fec6368
raw
history blame
7.56 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ReachyMini Controller</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 100%;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.status {
padding: 12px;
border-radius: 8px;
margin: 20px 0;
font-weight: 500;
text-align: center;
}
.status.disconnected {
background: #fee;
color: #c33;
}
.status.connected {
background: #efe;
color: #3c3;
}
.status.connecting {
background: #ffeaa7;
color: #d63031;
}
.connect-btn {
width: 100%;
padding: 15px;
background: #667eea;
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 30px;
}
.connect-btn:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.connect-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.commands {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 15px;
}
.command-btn {
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.command-btn:hover:not(:disabled) {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
.command-btn:active:not(:disabled) {
transform: translateY(-1px);
}
.command-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.log {
margin-top: 30px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
max-height: 200px;
overflow-y: auto;
font-size: 12px;
font-family: 'Courier New', monospace;
}
.log-entry {
padding: 5px 0;
border-bottom: 1px solid #e9ecef;
}
.log-entry:last-child {
border-bottom: none;
}
.log-entry.error {
color: #c33;
}
.log-entry.success {
color: #3c3;
}
.note {
margin-top: 20px;
padding: 15px;
background: #fff3cd;
border-left: 4px solid #ffc107;
border-radius: 4px;
font-size: 14px;
color: #856404;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 ReachyMini Controller</h1>
<div id="status" class="status disconnected">
Disconnected
</div>
<button id="connectBtn" class="connect-btn">
Connect to ReachyMini
</button>
<div class="commands">
<button class="command-btn" data-command="forward" disabled>Forward</button>
<button class="command-btn" data-command="backward" disabled>Backward</button>
<button class="command-btn" data-command="left" disabled>Turn Left</button>
<button class="command-btn" data-command="right" disabled>Turn Right</button>
<button class="command-btn" data-command="stop" disabled>Stop</button>
<button class="command-btn" data-command="wave" disabled>Wave</button>
</div>
<div class="log" id="log"></div>
<div class="note">
<strong>Note:</strong> Web Bluetooth requires HTTPS. Make sure you're accessing this page securely. Your
browser must also support Web Bluetooth API (Chrome, Edge, Opera).
</div>
</div>
<script>
let device = null;
let characteristic = null;
const statusEl = document.getElementById('status');
const connectBtn = document.getElementById('connectBtn');
const commandBtns = document.querySelectorAll('.command-btn');
const logEl = document.getElementById('log');
// Check if Web Bluetooth is supported
if (!navigator.bluetooth) {
updateStatus('Web Bluetooth not supported', 'disconnected');
addLog('ERROR: Web Bluetooth API not available in this browser', 'error');
connectBtn.disabled = true;
}
function addLog(message, type = '') {
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
const timestamp = new Date().toLocaleTimeString();
entry.textContent = `[${timestamp}] ${message}`;
logEl.appendChild(entry);
logEl.scrollTop = logEl.scrollHeight;
}
function updateStatus(message, state) {
statusEl.textContent = message;
statusEl.className = `status ${state}`;
}
async function connectToDevice() {
try {
updateStatus('Scanning for devices...', 'connecting');
addLog('Requesting Bluetooth device...');
device = await navigator.bluetooth.requestDevice({
filters: [{ name: 'ReachyMini' }],
optionalServices: ['0000ffe0-0000-1000-8000-00805f9b34fb'] // Common serial service UUID
});
addLog(`Found device: ${device.name}`);
updateStatus('Connecting...', 'connecting');
const server = await device.gatt.connect();
addLog('Connected to GATT server');
// Get the service (using common serial service UUID)
const service = await server.getPrimaryService('0000ffe0-0000-1000-8000-00805f9b34fb');
addLog('Got service');
// Get the characteristic (using common serial characteristic UUID)
characteristic = await service.getCharacteristic('0000ffe1-0000-1000-8000-00805f9b34fb');
addLog('Got characteristic');
updateStatus('Connected to ReachyMini', 'connected');
addLog('Successfully connected!', 'success');
connectBtn.textContent = 'Disconnect';
commandBtns.forEach(btn => btn.disabled = false);
device.addEventListener('gattserverdisconnected', onDisconnected);
} catch (error) {
addLog(`Connection failed: ${error.message}`, 'error');
updateStatus('Connection failed', 'disconnected');
console.error('Connection error:', error);
}
}
function onDisconnected() {
updateStatus('Disconnected', 'disconnected');
addLog('Device disconnected', 'error');
connectBtn.textContent = 'Connect to ReachyMini';
commandBtns.forEach(btn => btn.disabled = true);
characteristic = null;
device = null;
}
async function disconnect() {
if (device && device.gatt.connected) {
device.gatt.disconnect();
addLog('Manually disconnected');
}
}
async function sendCommand(command) {
if (!characteristic) {
addLog('Not connected to device', 'error');
return;
}
try {
const encoder = new TextEncoder();
const data = encoder.encode(command);
await characteristic.writeValue(data);
addLog(`Sent command: ${command}`, 'success');
} catch (error) {
addLog(`Failed to send command: ${error.message}`, 'error');
console.error('Send error:', error);
}
}
connectBtn.addEventListener('click', async () => {
if (device && device.gatt.connected) {
await disconnect();
} else {
await connectToDevice();
}
});
commandBtns.forEach(btn => {
btn.addEventListener('click', () => {
const command = btn.dataset.command;
sendCommand(command);
});
});
addLog('Ready. Click "Connect to ReachyMini" to start.');
</script>
</body>
</html>