Guard-fenix_CodigoFenix_2026/_Sort/BME680Index - Copy.html

186 lines
5.6 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ESP32 BME680 BLE Dashboard</title>
<style>
body { font-family: sans-serif; max-width: 760px; margin: 24px auto; padding: 0 16px; }
button { padding: 12px 16px; margin: 6px 6px 6px 0; font-size: 16px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.card { border: 1px solid #ddd; border-radius: 10px; padding: 14px; }
.big { font-size: 34px; font-weight: 700; }
.label { color: #666; margin-bottom: 6px; }
.status { white-space: pre-wrap; background:#f7f7f7; padding:10px; border-radius:8px; }
@media (max-width: 640px) { .grid { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<h2>ESP32 BME680 (BLE)</h2>
<div class="card">
<button id="btnConnect">Connect</button>
<button id="btnDisconnect" disabled>Disconnect</button>
<div>Device: <span id="deviceName"></span></div>
<div>Connection: <span id="connState">disconnected</span></div>
</div>
<div class="grid" style="margin-top:12px;">
<div class="card">
<div class="label">Temperature</div>
<div class="big"><span id="t"></span> °C</div>
</div>
<div class="card">
<div class="label">Humidity</div>
<div class="big"><span id="h"></span> %</div>
</div>
<div class="card">
<div class="label">Pressure</div>
<div class="big"><span id="p"></span> hPa</div>
</div>
<div class="card">
<div class="label">Gas Resistance</div>
<div class="big"><span id="g"></span> Ω</div>
<div>Valid: <span id="gv"></span></div>
</div>
</div>
<div class="card" style="margin-top:12px;">
<div class="label">LED Control</div>
<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 class="card" style="margin-top:12px;">
<div class="label">Log</div>
<div class="status" id="log"></div>
</div>
<script>
(() => {
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';
const $ = (id) => document.getElementById(id);
const logEl = $('log');
const td = new TextDecoder('utf-8');
let device = null;
let server = null;
let sensorChar = null;
let ledChar = null;
function log(...args) {
const msg = args.join(' ');
logEl.textContent = (msg + '\n' + logEl.textContent).slice(0, 5000);
console.log(...args);
}
function setConnectedUI(isConnected) {
$('connState').textContent = isConnected ? 'connected' : 'disconnected';
$('btnDisconnect').disabled = !isConnected;
document.querySelectorAll('button.cmd').forEach(b => b.disabled = !isConnected);
}
function updateUI(obj) {
if (typeof obj.t_c === 'number') $('t').textContent = obj.t_c.toFixed(2);
if (typeof obj.h_pct === 'number') $('h').textContent = obj.h_pct.toFixed(2);
if (typeof obj.p_hpa === 'number') $('p').textContent = obj.p_hpa.toFixed(2);
if (obj.gas_ohms === null || obj.gas_ohms === undefined) {
$('g').textContent = '—';
} else {
$('g').textContent = String(obj.gas_ohms);
}
$('gv').textContent = obj.gas_valid ? 'true' : 'false';
}
function onNotify(event) {
try {
const dv = event.target.value;
const bytes = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength);
const text = td.decode(bytes).trim();
const obj = JSON.parse(text);
updateUI(obj);
} catch (e) {
log('Notify decode/JSON error:', e);
}
}
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] }],
optionalServices: [SERVICE_UUID]
});
$('deviceName').textContent = device.name || '(no name)';
device.addEventListener('gattserverdisconnected', onDisconnected);
log('Connecting GATT...');
server = await device.gatt.connect();
const service = await server.getPrimaryService(SERVICE_UUID);
sensorChar = await service.getCharacteristic(SENSOR_CHAR_UUID);
ledChar = await service.getCharacteristic(LED_CHAR_UUID);
log('Starting notifications...');
await sensorChar.startNotifications();
sensorChar.addEventListener('characteristicvaluechanged', onNotify);
setConnectedUI(true);
log('Connected.');
} catch (e) {
log('Connect error:', e);
setConnectedUI(false);
}
}
async function sendLed(cmd) {
try {
if (!ledChar) return;
const payload = new Uint8Array([cmd & 0xFF]);
await ledChar.writeValue(payload);
log('LED cmd:', cmd);
} catch (e) {
log('Write error:', e);
}
}
function onDisconnected() {
log('Device disconnected.');
setConnectedUI(false);
}
async function disconnect() {
try {
if (device?.gatt?.connected) device.gatt.disconnect();
} finally {
setConnectedUI(false);
}
}
$('btnConnect').addEventListener('click', connect);
$('btnDisconnect').addEventListener('click', disconnect);
document.querySelectorAll('button.cmd').forEach(btn => {
btn.addEventListener('click', () => sendLed(parseInt(btn.dataset.cmd, 10)));
});
setConnectedUI(false);
})();
</script>
</body>
</html>