maj bluetooth compatible webapp

This commit is contained in:
thomas committed 2026-10-24 00:54:19 +02:00
1 parent 3757975e51
commit e94499ba1e
22 files changed
+68217 -67436

No files matched your search

+55 -19
View File
@@ -69,7 +69,7 @@ const SECTIONS = [
const state = new Map(); /* id -> valeur fil */
const widgets = new Map(); /* id -> fonction de mise a jour de l'UI */
let ctrl = null, pending = new Map(), timer = 0, demo = false;
let link = null, pending = new Map(), timer = 0, demo = false;
const $ = s => document.querySelector(s);
const msg = t => { $("#msg").textContent = t || ""; };
@@ -124,45 +124,81 @@ function send(id, value) {
async function flush() {
timer = 0;
const items = [...pending]; pending = new Map();
if (!ctrl) return;
if (!link) return;
try {
for (let i = 0; i < items.length; i += MAX_RECORDS) {
const chunk = items.slice(i, i + MAX_RECORDS);
const buf = new DataView(new ArrayBuffer(chunk.length * 3));
chunk.forEach(([id, v], k) => { buf.setUint8(k * 3, id); buf.setInt16(k * 3 + 1, v, true); });
await ctrl.writeValueWithoutResponse(buf);
await link.write(new Uint8Array(buf.buffer), false);
}
} catch (e) { msg("Envoi impossible : " + e.message); }
}
function onState(ev) {
const dv = ev.target.value;
function onState(dv) {
for (let o = 0; o + 3 <= dv.byteLength; o += 3) {
const id = dv.getUint8(o), v = dv.getInt16(o + 1, true);
state.set(id, v); widgets.get(id)?.(v);
}
}
/* ---- connexion ---- */
/* ---- connexion ----
* Deux transports, meme interface {write(bytes, avecReponse), close()} :
* - app WebApp : pont natif Android.ble (Web Bluetooth n'existe pas dans sa WebView)
* - Chrome : Web Bluetooth (page en contexte securise) */
function setConnected(on) { $("#dot").classList.toggle("on", on); $("#btn").textContent = on ? "Déconnecter" : "Connecter"; }
let device = null;
function lost() { link = null; setConnected(false); msg("Déconnecté."); }
async function connectNative() {
const B = window.Android.ble;
const dev = await B.connect({name: "CelloFX", service: SERVICE, timeout: 10000});
Android.on("ble.disconnect", e => { if (e.id === dev.id) lost(); });
await B.subscribe(dev.id, SERVICE, CHAR_STATE, bytes => onState(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)));
link = {
write: (bytes, resp) => B.write(dev.id, SERVICE, CHAR_CTRL, bytes, {response: resp}),
close: () => B.disconnect(dev.id),
};
await link.write(new Uint8Array([SNAPSHOT, 0, 0]), true);
}
async function connectWeb() {
const device = await navigator.bluetooth.requestDevice({filters: [{name: "CelloFX"}], optionalServices: [SERVICE]});
device.addEventListener("gattserverdisconnected", lost);
const server = await device.gatt.connect();
const svc = await server.getPrimaryService(SERVICE);
const ctrl = await svc.getCharacteristic(CHAR_CTRL);
const st = await svc.getCharacteristic(CHAR_STATE);
st.addEventListener("characteristicvaluechanged", ev => onState(ev.target.value));
await st.startNotifications(); /* le module envoie alors un instantane */
link = {
write: (bytes, resp) => resp ? ctrl.writeValueWithResponse(bytes) : ctrl.writeValueWithoutResponse(bytes),
close: () => device.gatt.connected && device.gatt.disconnect(),
};
await link.write(new Uint8Array([SNAPSHOT, 0, 0]), true); /* au cas ou */
}
async function connect() {
if (!navigator.bluetooth) { demo = true; setConnected(true); msg("Web Bluetooth indisponible ici : mode démo (rien n'est envoyé). Ouvre la page dans Chrome Android en HTTPS."); return; }
const native = window.Android?.isNative && window.Android.ble;
if (!native && !navigator.bluetooth) {
demo = true; setConnected(true);
msg(window.isSecureContext
? "Web Bluetooth indisponible dans ce navigateur : mode démo (rien n'est envoyé). Utilise l'app WebApp ou Chrome Android."
: "Page non sécurisée : Web Bluetooth est bloqué (mode démo). Sers la page en HTTPS ou autorise l'origine dans chrome://flags (voir remote/serve.sh).");
return;
}
try {
msg("Recherche de CelloFX…");
device = await navigator.bluetooth.requestDevice({filters: [{name: "CelloFX"}], optionalServices: [SERVICE]});
device.addEventListener("gattserverdisconnected", () => { ctrl = null; setConnected(false); msg("Déconnecté."); });
const server = await device.gatt.connect();
const svc = await server.getPrimaryService(SERVICE);
ctrl = await svc.getCharacteristic(CHAR_CTRL);
const st = await svc.getCharacteristic(CHAR_STATE);
st.addEventListener("characteristicvaluechanged", onState);
await st.startNotifications(); /* le module envoie alors un instantane */
await ctrl.writeValueWithResponse(new Uint8Array([SNAPSHOT, 0, 0])); /* au cas ou */
await (native ? connectNative() : connectWeb());
setConnected(true); msg("Connecté.");
} catch (e) { setConnected(false); msg(e.name === "NotFoundError" ? "Aucun appareil choisi." : "Connexion échouée : " + e.message); }
} catch (e) {
link = null; setConnected(false);
msg(e.name === "NotFoundError" ? "Aucun appareil choisi."
: e.code === "not_found" ? "CelloFX introuvable : la carte est-elle allumée et à portée ?"
: e.code === "permission_denied" ? "Autorisation Bluetooth refusée (menu WebApp → Autorisations)."
: "Connexion échouée : " + e.message);
}
}
function disconnect() { device?.gatt?.connected && device.gatt.disconnect(); ctrl = null; demo = false; setConnected(false); msg(""); }
function disconnect() { link?.close(); link = null; demo = false; setConnected(false); msg(""); }
$("#btn").addEventListener("click", () => ($("#dot").classList.contains("on") ? disconnect() : connect()));
build();
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Sert la telecommande CelloFX sur le reseau local (telephone et PC sur le meme Wi-Fi).
# Web Bluetooth exige un contexte securise : voir le message affiche ci-dessous.
# Usage: remote/serve.sh [port]
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
PORT="${1:-8000}"
echo "Page : cellofx-remote.html (port $PORT)"
for ip in $(hostname -I 2>/dev/null); do echo " http://$ip:$PORT/cellofx-remote.html"; done
cat <<MSG
Sur le telephone (Chrome Android, une seule fois) :
1. chrome://flags/#unsafely-treat-insecure-origin-as-secure
2. y ajouter l'adresse http://<ip>:$PORT ci-dessus, passer le drapeau sur Enabled, relancer Chrome
3. Bluetooth + localisation actives, ouvrir l'adresse, "Connecter"
Ensuite : menu Chrome > "Ajouter a l'ecran d'accueil" (la page est alors lancee comme une app).
MSG
exec python3 -m http.server "$PORT" --bind 0.0.0.0