Guard-fenix_CodigoFenix_2026/SupportProjects/ModbusRTU_EnvSim.html

185 lines
5.5 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FLL Umbrella Env BLE Control</title>
<style>
body { font-family: system-ui, sans-serif; margin: 16px; }
.row { margin: 10px 0; }
label { display:block; margin-bottom:6px; }
input[type="range"] { width: 100%; }
button { padding: 10px 12px; margin-right: 8px; }
code { background:#f3f3f3; padding:2px 6px; border-radius:6px; }
.pill { display:inline-block; padding:2px 8px; border-radius: 999px; background:#eee; }
</style>
</head>
<body>
<h2>FLL Umbrella Env BLE Control</h2>
<div class="row">
<button id="btnConnect">Connect</button>
<button id="btnDisconnect" disabled>Disconnect</button>
<span class="pill" id="status">Disconnected</span>
</div>
<div class="row">
<button id="btnLive" disabled>Set LIVE</button>
<button id="btnSim" disabled>Set SIM</button>
</div>
<div class="row">
<label>Temp (°C): <span id="tVal"></span></label>
<input id="temp" type="range" min="-10.00" max="60.00" step="0.01" value="25.00">
</div>
<div class="row">
<label>RH (%): <span id="rhVal"></span></label>
<input id="rh" type="range" min="0.00" max="100.00" step="0.01" value="50.00">
</div>
<div class="row">
<label>Pressure (hPa): <span id="pVal"></span></label>
<input id="press" type="range" min="900.0" max="1100.0" step="0.1" value="1013.2">
</div>
<div class="row">
<label>IAQ override (0..500) [optional]: <span id="iaqVal"></span></label>
<input id="iaq" type="range" min="0" max="500" step="1" value="25">
</div>
<div class="row">
<button id="btnSend" disabled>Send SIM Values</button>
</div>
<p>
BLE Service: <code>12345678-1234-5678-1234-56789abcdef0</code><br/>
Characteristic: <code>12345678-1234-5678-1234-56789abcdef1</code>
</p>
<script>
const UUID_SVC = "12345678-1234-5678-1234-56789abcdef0";
const UUID_CHR = "12345678-1234-5678-1234-56789abcdef1";
let device = null;
let server = null;
let chr = null;
const elStatus = document.getElementById("status");
const btnConnect = document.getElementById("btnConnect");
const btnDisconnect = document.getElementById("btnDisconnect");
const btnLive = document.getElementById("btnLive");
const btnSim = document.getElementById("btnSim");
const btnSend = document.getElementById("btnSend");
const sTemp = document.getElementById("temp");
const sRh = document.getElementById("rh");
const sPress = document.getElementById("press");
const sIaq = document.getElementById("iaq");
const tVal = document.getElementById("tVal");
const rhVal = document.getElementById("rhVal");
const pVal = document.getElementById("pVal");
const iaqVal = document.getElementById("iaqVal");
function setUiConnected(connected) {
btnDisconnect.disabled = !connected;
btnLive.disabled = !connected;
btnSim.disabled = !connected;
btnSend.disabled = !connected;
elStatus.textContent = connected ? "Connected" : "Disconnected";
}
function refreshLabels() {
tVal.textContent = Number(sTemp.value).toFixed(2);
rhVal.textContent = Number(sRh.value).toFixed(2);
pVal.textContent = Number(sPress.value).toFixed(1);
iaqVal.textContent = Number(sIaq.value).toFixed(0);
}
[sTemp, sRh, sPress, sIaq].forEach(x => x.addEventListener("input", refreshLabels));
refreshLabels();
async function writeBytes(u8) {
if (!chr) throw new Error("Not connected");
// Use writeValueWithoutResponse if available, otherwise writeValue
if (chr.writeValueWithoutResponse) return chr.writeValueWithoutResponse(u8);
return chr.writeValue(u8);
}
function packMode(mode) {
return new Uint8Array([0x01, mode ? 1 : 0]);
}
function packSim(tempC, rhPct, pressHpa, iaqOverride) {
const temp_x100 = Math.round(tempC * 100);
const rh_x100 = Math.round(rhPct * 100);
const press_x10 = Math.round(pressHpa * 10);
const iaq = Math.round(iaqOverride);
const buf = new ArrayBuffer(9);
const dv = new DataView(buf);
dv.setUint8(0, 0x02);
dv.setInt16(1, temp_x100, true);
dv.setUint16(3, rh_x100, true);
dv.setUint16(5, press_x10, true);
dv.setUint16(7, iaq, true);
return new Uint8Array(buf);
}
btnConnect.addEventListener("click", async () => {
try {
device = await navigator.bluetooth.requestDevice({
filters: [{ services: [UUID_SVC] }],
optionalServices: [UUID_SVC]
});
device.addEventListener("gattserverdisconnected", () => {
server = null; chr = null;
setUiConnected(false);
});
server = await device.gatt.connect();
const svc = await server.getPrimaryService(UUID_SVC);
chr = await svc.getCharacteristic(UUID_CHR);
setUiConnected(true);
} catch (e) {
console.error(e);
alert("Connect failed: " + e);
setUiConnected(false);
}
});
btnDisconnect.addEventListener("click", async () => {
try {
if (device?.gatt?.connected) device.gatt.disconnect();
} catch {}
server = null; chr = null;
setUiConnected(false);
});
btnLive.addEventListener("click", async () => {
try {
await writeBytes(packMode(0));
} catch (e) { alert("Write failed: " + e); }
});
btnSim.addEventListener("click", async () => {
try {
await writeBytes(packMode(1));
} catch (e) { alert("Write failed: " + e); }
});
btnSend.addEventListener("click", async () => {
try {
const t = Number(sTemp.value);
const rh = Number(sRh.value);
const p = Number(sPress.value);
const iaq = Number(sIaq.value);
await writeBytes(packSim(t, rh, p, iaq));
} catch (e) { alert("Write failed: " + e); }
});
setUiConnected(false);
</script>
</body>
</html>