diff --git a/web/app.js b/web/app.js index 8f0e99d..5be4743 100644 --- a/web/app.js +++ b/web/app.js @@ -15,7 +15,31 @@ const config = { sensitivity:600, deadZone:0.06, accelStrength:0.08, curve:0, ax let device=null, server=null, chars={}, userDisconnected=false; let currentChargeStatus=0, currentBattPct=null; +// ── GATT write queue (prevents "operation already in progress") ─────────────── +let _gattQueue = Promise.resolve(); +function gattWrite(char, value) { + const p = _gattQueue.then(() => char.writeValueWithResponse(value)); + _gattQueue = p.catch(() => {}); + return p; +} +function gattCmd(char, value) { + const p = _gattQueue.then(() => char.writeValueWithoutResponse(value)); + _gattQueue = p.catch(() => {}); + return p; +} + // ── Logging ────────────────────────────────────────────────────────────────── +(function() { + const _methods = { log: '', warn: 'warn', error: 'err' }; + for (const [method, type] of Object.entries(_methods)) { + const orig = console[method].bind(console); + console[method] = (...args) => { + orig(...args); + log(args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '), type); + }; + } +})(); + function log(msg, type='') { const el=document.getElementById('console'); const now=new Date(); @@ -153,7 +177,12 @@ function applyConfigToUI() { document.getElementById('tapModGui').checked = !!(config.tapMod & 0x08); } -async function writeConfigBlob() { +let _writeConfigTimer = null; +function writeConfigBlob() { + clearTimeout(_writeConfigTimer); + _writeConfigTimer = setTimeout(_doWriteConfigBlob, 150); +} +async function _doWriteConfigBlob() { if (!chars.configBlob) return; // Gather current UI values into the config shadow @@ -184,7 +213,7 @@ async function writeConfigBlob() { view.setUint8(19, 0); try { - await chars.configBlob.writeValueWithResponse(buf); + await gattWrite(chars.configBlob, buf); log(`Config written — sens=${config.sensitivity.toFixed(0)} tapThr=${config.tapThreshold} tapAction=${config.tapAction}`,'ok'); } catch(e) { log(`Config write failed: ${e.message}`,'err'); } } @@ -192,10 +221,10 @@ async function writeConfigBlob() { // ── Individual control handlers ─────────────────────────────────────────────── // These update the local config shadow then write the full blob -async function setCurve(val) { +function setCurve(val) { config.curve = val; setCurveUI(val); - await writeConfigBlob(); + writeConfigBlob(); log(`Curve → ${['LINEAR','SQUARE','SQRT'][val]}`,'ok'); } function setCurveUI(val) { @@ -203,10 +232,10 @@ function setCurveUI(val) { document.getElementById(id).classList.toggle('active', i===val)); } -async function setChargeMode(val) { +function setChargeMode(val) { config.chargeMode = val; setChargeModeUI(val); - await writeConfigBlob(); + writeConfigBlob(); log(`Charge → ${['OFF','SLOW 50mA','FAST 100mA'][val]}`,'warn'); } function setChargeModeUI(val) { @@ -218,10 +247,10 @@ function setChargeModeUI(val) { document.getElementById('ciMode').textContent = ['Off (0mA)','50 mA','100 mA'][val] ?? '--'; } -async function setTapAction(val) { +function setTapAction(val) { config.tapAction = val; setTapActionUI(val); - await writeConfigBlob(); + writeConfigBlob(); } function setTapActionUI(val) { ['tapActLeft','tapActRight','tapActMiddle','tapActKey'].forEach((id,i) => @@ -237,7 +266,7 @@ function onTapKeyInput() { async function sendCalibrate() { if (!chars.command) return; - try { await chars.command.writeValueWithoutResponse(new Uint8Array([0x01])); log('Calibration sent — hold still!','warn'); } + try { await gattCmd(chars.command, new Uint8Array([0x01])); log('Calibration sent — hold still!','warn'); } catch(e) { log(`Calibrate failed: ${e.message}`,'err'); } } function confirmReset() { document.getElementById('overlay').classList.add('show'); } @@ -245,7 +274,7 @@ function closeModal() { document.getElementById('overlay').classList.remove('s async function doReset() { closeModal(); if (!chars.command) return; try { - await chars.command.writeValueWithoutResponse(new Uint8Array([0xFF])); + await gattCmd(chars.command, new Uint8Array([0xFF])); log('Factory reset sent…','warn'); setTimeout(async () => { await readConfigBlob(); log('Config reloaded','ok'); }, 1500); } catch(e) { log(`Reset failed: ${e.message}`,'err'); }