191 lines
5.5 KiB
HTML
191 lines
5.5 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>ESP32 BLE Control</title>
|
|
<style>
|
|
body { font-family: sans-serif; max-width: 680px; margin: 24px auto; padding: 0 16px; }
|
|
button { padding: 12px 16px; margin: 6px 6px 6px 0; font-size: 16px; }
|
|
.row { margin-top: 10px; }
|
|
.card { border: 1px solid #ddd; border-radius: 10px; padding: 14px; margin-top: 12px; }
|
|
.value { font-size: 40px; font-weight: 700; }
|
|
.status { white-space: pre-wrap; background:#f7f7f7; padding:10px; border-radius:8px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>ESP32 BLE Random Value + LED Control</h2>
|
|
|
|
<div class="card">
|
|
<div class="row">
|
|
<button id="btnConnect">Connect</button>
|
|
<button id="btnDisconnect" disabled>Disconnect</button>
|
|
</div>
|
|
<div class="row">
|
|
<div>Device: <span id="deviceName">—</span></div>
|
|
<div>Connection: <span id="connState">disconnected</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div>Random value (notify):</div>
|
|
<div class="value" id="randValue">—</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div>LED command (write):</div>
|
|
<div class="row">
|
|
<button class="cmd" data-cmd="0" disabled>OFF</button>
|
|
<button class="cmd" data-cmd="1" disabled>RED</button>
|
|
<button class="cmd" data-cmd="2" disabled>GREEN</button>
|
|
<button class="cmd" data-cmd="3" disabled>BLUE</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div>Log</div>
|
|
<div class="status" id="log"></div>
|
|
</div>
|
|
|
|
<script>
|
|
(() => {
|
|
// ====== YOUR UUIDs (from your MicroPython code) ======
|
|
const SERVICE_UUID = '19b10000-e8f2-537e-4f6c-d104768a1214';
|
|
const SENSOR_CHAR_UUID = '19b10001-e8f2-537e-4f6c-d104768a1214';
|
|
const LED_CHAR_UUID = '19b10002-e8f2-537e-4f6c-d104768a1214';
|
|
|
|
// UI
|
|
const $ = (id) => document.getElementById(id);
|
|
const logEl = $('log');
|
|
const randEl = $('randValue');
|
|
const connEl = $('connState');
|
|
const devNameEl = $('deviceName');
|
|
const btnConnect = $('btnConnect');
|
|
const btnDisconnect = $('btnDisconnect');
|
|
const cmdButtons = Array.from(document.querySelectorAll('button.cmd'));
|
|
|
|
let device = null;
|
|
let server = null;
|
|
let sensorChar = null;
|
|
let ledChar = null;
|
|
|
|
const td = new TextDecoder('utf-8');
|
|
|
|
function log(...args) {
|
|
const msg = args.join(' ');
|
|
logEl.textContent = (msg + '\n' + logEl.textContent).slice(0, 4000);
|
|
console.log(...args);
|
|
}
|
|
|
|
function setConnectedUI(isConnected) {
|
|
connEl.textContent = isConnected ? 'connected' : 'disconnected';
|
|
btnDisconnect.disabled = !isConnected;
|
|
cmdButtons.forEach(b => b.disabled = !isConnected);
|
|
}
|
|
|
|
async function connect() {
|
|
try {
|
|
if (!navigator.bluetooth) {
|
|
alert('Web Bluetooth not available. Use Chrome/Edge (Windows) or Chrome (Android).');
|
|
return;
|
|
}
|
|
|
|
log('Requesting device...');
|
|
device = await navigator.bluetooth.requestDevice({
|
|
filters: [{ services: [SERVICE_UUID] }],
|
|
// If you want to match by name instead, you can do:
|
|
// filters: [{ name: 'ESP32' }],
|
|
optionalServices: [SERVICE_UUID]
|
|
});
|
|
|
|
devNameEl.textContent = device.name || '(no name)';
|
|
device.addEventListener('gattserverdisconnected', onDisconnected);
|
|
|
|
log('Connecting GATT...');
|
|
server = await device.gatt.connect();
|
|
|
|
log('Getting service...');
|
|
const service = await server.getPrimaryService(SERVICE_UUID);
|
|
|
|
log('Getting characteristics...');
|
|
sensorChar = await service.getCharacteristic(SENSOR_CHAR_UUID);
|
|
ledChar = await service.getCharacteristic(LED_CHAR_UUID);
|
|
|
|
// Subscribe to notifications for random value
|
|
log('Starting notifications for sensor characteristic...');
|
|
await sensorChar.startNotifications();
|
|
sensorChar.addEventListener('characteristicvaluechanged', onSensorNotify);
|
|
|
|
setConnectedUI(true);
|
|
log('Connected.');
|
|
} catch (e) {
|
|
log('Connect error:', e);
|
|
setConnectedUI(false);
|
|
}
|
|
}
|
|
|
|
function onSensorNotify(event) {
|
|
try {
|
|
const dv = event.target.value; // DataView
|
|
const bytes = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength);
|
|
|
|
// Your ESP32 sends UTF-8 text like b"42"
|
|
const text = td.decode(bytes).trim();
|
|
randEl.textContent = text;
|
|
|
|
// If you prefer number parsing:
|
|
// const num = parseInt(text, 10);
|
|
// randEl.textContent = Number.isFinite(num) ? String(num) : text;
|
|
|
|
} catch (e) {
|
|
log('Notify decode error:', e);
|
|
}
|
|
}
|
|
|
|
async function sendLedCommand(cmd) {
|
|
try {
|
|
if (!ledChar) return;
|
|
|
|
// Your ESP32 expects int.from_bytes(data,'big') -> send 1 byte 0..3
|
|
const payload = new Uint8Array([cmd & 0xFF]);
|
|
await ledChar.writeValue(payload);
|
|
log('Wrote LED cmd:', cmd);
|
|
} catch (e) {
|
|
log('Write error:', e);
|
|
}
|
|
}
|
|
|
|
async function disconnect() {
|
|
try {
|
|
if (device?.gatt?.connected) {
|
|
log('Disconnecting...');
|
|
device.gatt.disconnect();
|
|
}
|
|
} catch (e) {
|
|
log('Disconnect error:', e);
|
|
} finally {
|
|
setConnectedUI(false);
|
|
}
|
|
}
|
|
|
|
function onDisconnected() {
|
|
log('Device disconnected.');
|
|
setConnectedUI(false);
|
|
}
|
|
|
|
// Wire UI
|
|
btnConnect.addEventListener('click', connect);
|
|
btnDisconnect.addEventListener('click', disconnect);
|
|
|
|
cmdButtons.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const cmd = parseInt(btn.dataset.cmd, 10);
|
|
sendLedCommand(cmd);
|
|
});
|
|
});
|
|
|
|
setConnectedUI(false);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|