reachy_mini_tools / index.html
cduss's picture
uuid and commands
ceef3cc
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Permissions-Policy" content="bluetooth=*">
<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;
}
.command-btn.danger {
background: linear-gradient(135deg, #ee5a6f 0%, #f29263 100%);
}
.command-btn.warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.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;
}
.response-box {
margin-top: 20px;
padding: 15px;
background: #e7f3ff;
border-left: 4px solid #2196F3;
border-radius: 4px;
font-size: 14px;
font-family: 'Courier New', monospace;
display: none;
}
.response-box.show {
display: block;
}
.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="PING" disabled>Ping</button>
<button class="command-btn" data-command="STATUS" disabled>Status</button>
<button class="command-btn" data-command="CMD_HOTSPOT" disabled>Hotspot</button>
<button class="command-btn danger" data-command="CMD_RESTART_DAEMON" disabled>Restart Daemon</button>
<button class="command-btn warning" data-command="CMD_SOFTWARE_RESET" disabled>Software Reset</button>
</div>
<div id="responseBox" class="response-box"></div>
<div class="log" id="log"></div>
<div class="note">
<strong>Note:</strong> This requires HTTPS and a Chromium-based browser with Web Bluetooth enabled. Access
from your phone's hotspot for best results.
</div>
</div>
<script>
let device = null;
let commandCharacteristic = null;
let responseCharacteristic = null;
const statusEl = document.getElementById('status');
const connectBtn = document.getElementById('connectBtn');
const commandBtns = document.querySelectorAll('.command-btn');
const logEl = document.getElementById('log');
const responseBox = document.getElementById('responseBox');
// Correct UUIDs from your server
const SERVICE_UUID = '12345678-1234-5678-1234-56789abcdef0';
const COMMAND_CHAR_UUID = '12345678-1234-5678-1234-56789abcdef1';
const RESPONSE_CHAR_UUID = '12345678-1234-5678-1234-56789abcdef2';
// 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}`;
}
function showResponse(message) {
responseBox.textContent = `Response: ${message}`;
responseBox.classList.add('show');
setTimeout(() => {
responseBox.classList.remove('show');
}, 5000);
}
async function connectToDevice() {
try {
updateStatus('Scanning for devices...', 'connecting');
addLog('Requesting Bluetooth device...');
device = await navigator.bluetooth.requestDevice({
filters: [{ name: 'ReachyMini' }],
optionalServices: [SERVICE_UUID]
});
addLog(`Found device: ${device.name}`);
updateStatus('Connecting...', 'connecting');
const server = await device.gatt.connect();
addLog('Connected to GATT server');
// Get the service
const service = await server.getPrimaryService(SERVICE_UUID);
addLog('Got service');
// Get the command characteristic (write)
commandCharacteristic = await service.getCharacteristic(COMMAND_CHAR_UUID);
addLog('Got command characteristic');
// Get the response characteristic (read/notify)
responseCharacteristic = await service.getCharacteristic(RESPONSE_CHAR_UUID);
addLog('Got response characteristic');
// Enable notifications for responses
await responseCharacteristic.startNotifications();
responseCharacteristic.addEventListener('characteristicvaluechanged', handleResponse);
addLog('Notifications enabled for responses');
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 handleResponse(event) {
const value = event.target.value;
const decoder = new TextDecoder('utf-8');
const response = decoder.decode(value);
addLog(`Response: ${response}`, 'success');
showResponse(response);
}
function onDisconnected() {
updateStatus('Disconnected', 'disconnected');
addLog('Device disconnected', 'error');
connectBtn.textContent = 'Connect to ReachyMini';
commandBtns.forEach(btn => btn.disabled = true);
commandCharacteristic = null;
responseCharacteristic = null;
device = null;
}
async function disconnect() {
if (device && device.gatt.connected) {
device.gatt.disconnect();
addLog('Manually disconnected');
}
}
async function sendCommand(command) {
if (!commandCharacteristic) {
addLog('Not connected to device', 'error');
return;
}
try {
const encoder = new TextEncoder();
const data = encoder.encode(command);
await commandCharacteristic.writeValue(data);
addLog(`Sent command: ${command}`, 'success');
// Try to read the response
try {
const value = await responseCharacteristic.readValue();
const decoder = new TextDecoder('utf-8');
const response = decoder.decode(value);
if (response) {
addLog(`Response: ${response}`, 'success');
showResponse(response);
}
} catch (readError) {
// Response will come via notification
addLog('Waiting for response notification...', '');
}
} 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>