Add configuration slider for double tapping

This commit is contained in:
2026-03-01 20:45:23 +01:00
parent ef97d8f32a
commit f155d16399
8 changed files with 248 additions and 52 deletions
+56 -13
View File
@@ -9,7 +9,8 @@ const CHR = {
};
// Local shadow of the current config (kept in sync with device)
const config = { sensitivity:600, deadZone:0.06, accelStrength:0.08, curve:0, axisFlip:0, chargeMode:1 };
const config = { sensitivity:600, deadZone:0.06, accelStrength:0.08, curve:0, axisFlip:0, chargeMode:1,
tapThreshold:12, tapAction:0, tapKey:0, tapMod:0 };
let device=null, server=null, chars={}, userDisconnected=false;
let currentChargeStatus=0, currentBattPct=null;
@@ -104,23 +105,30 @@ async function discoverServices() {
}
// ── ConfigBlob read / write ──────────────────────────────────────────────────
// ConfigBlob layout (16 bytes LE):
// ConfigBlob layout (20 bytes LE):
// float sensitivity [0], float deadZone [4], float accelStrength [8]
// uint8 curve [12], uint8 axisFlip [13], uint8 chargeMode [14], uint8 pad [15]
// uint8 curve [12], uint8 axisFlip [13], uint8 chargeMode [14]
// uint8 tapThreshold [15], uint8 tapAction [16], uint8 tapKey [17], uint8 tapMod [18], uint8 pad [19]
async function readConfigBlob() {
if (!chars.configBlob) return;
try {
const dv = await chars.configBlob.readValue();
const view = new DataView(dv.buffer ?? dv);
const view = dv instanceof DataView ? new DataView(dv.buffer, dv.byteOffset, dv.byteLength) : new DataView(dv);
config.sensitivity = view.getFloat32(0, true);
config.deadZone = view.getFloat32(4, true);
config.accelStrength = view.getFloat32(8, true);
config.curve = view.getUint8(12);
config.axisFlip = view.getUint8(13);
config.chargeMode = view.getUint8(14);
if (view.byteLength >= 20) {
config.tapThreshold = view.getUint8(15);
config.tapAction = view.getUint8(16);
config.tapKey = view.getUint8(17);
config.tapMod = view.getUint8(18);
}
applyConfigToUI();
log(`Config loaded — sens=${config.sensitivity.toFixed(0)} dz=${config.deadZone.toFixed(3)}`,'ok');
log(`Config loaded — sens=${config.sensitivity.toFixed(0)} dz=${config.deadZone.toFixed(3)} tapThr=${config.tapThreshold}`,'ok');
} catch(e) { log(`Config read error: ${e.message}`,'err'); }
}
@@ -135,6 +143,14 @@ function applyConfigToUI() {
document.getElementById('flipX').checked = !!(config.axisFlip & 1);
document.getElementById('flipY').checked = !!(config.axisFlip & 2);
setChargeModeUI(config.chargeMode);
document.getElementById('slTapThreshold').value = config.tapThreshold;
updateDisplay('tapThreshold', config.tapThreshold);
setTapActionUI(config.tapAction);
document.getElementById('tapKeyHex').value = config.tapKey ? config.tapKey.toString(16).padStart(2,'0') : '';
document.getElementById('tapModCtrl').checked = !!(config.tapMod & 0x01);
document.getElementById('tapModShift').checked = !!(config.tapMod & 0x02);
document.getElementById('tapModAlt').checked = !!(config.tapMod & 0x04);
document.getElementById('tapModGui').checked = !!(config.tapMod & 0x08);
}
async function writeConfigBlob() {
@@ -146,9 +162,14 @@ async function writeConfigBlob() {
config.accelStrength = +document.getElementById('slAccel').value;
config.axisFlip = (document.getElementById('flipX').checked ? 1 : 0)
| (document.getElementById('flipY').checked ? 2 : 0);
// config.curve and config.chargeMode are updated directly by setCurve/setChargeMode
config.tapThreshold = +document.getElementById('slTapThreshold').value;
config.tapMod = (document.getElementById('tapModCtrl').checked ? 0x01 : 0)
| (document.getElementById('tapModShift').checked ? 0x02 : 0)
| (document.getElementById('tapModAlt').checked ? 0x04 : 0)
| (document.getElementById('tapModGui').checked ? 0x08 : 0);
// config.curve, config.chargeMode, config.tapAction, config.tapKey updated directly
const buf = new ArrayBuffer(16);
const buf = new ArrayBuffer(20);
const view = new DataView(buf);
view.setFloat32(0, config.sensitivity, true);
view.setFloat32(4, config.deadZone, true);
@@ -156,11 +177,15 @@ async function writeConfigBlob() {
view.setUint8(12, config.curve);
view.setUint8(13, config.axisFlip);
view.setUint8(14, config.chargeMode);
view.setUint8(15, 0);
view.setUint8(15, config.tapThreshold);
view.setUint8(16, config.tapAction);
view.setUint8(17, config.tapKey);
view.setUint8(18, config.tapMod);
view.setUint8(19, 0);
try {
await chars.configBlob.writeValue(buf);
log(`Config written — sens=${config.sensitivity.toFixed(0)} dz=${config.deadZone.toFixed(3)} curve=${config.curve} chg=${config.chargeMode}`,'ok');
log(`Config written — sens=${config.sensitivity.toFixed(0)} tapThr=${config.tapThreshold} tapAction=${config.tapAction}`,'ok');
} catch(e) { log(`Config write failed: ${e.message}`,'err'); }
}
@@ -193,6 +218,23 @@ function setChargeModeUI(val) {
document.getElementById('ciMode').textContent = ['Off (0mA)','50 mA','100 mA'][val] ?? '--';
}
async function setTapAction(val) {
config.tapAction = val;
setTapActionUI(val);
await writeConfigBlob();
}
function setTapActionUI(val) {
['tapActLeft','tapActRight','tapActMiddle','tapActKey'].forEach((id,i) =>
document.getElementById(id).classList.toggle('active', i===val));
document.getElementById('tapKeyRow').style.display = val===3 ? 'flex' : 'none';
}
function onTapKeyInput() {
const hex = document.getElementById('tapKeyHex').value.trim();
const code = parseInt(hex, 16);
config.tapKey = (isNaN(code) || code < 0 || code > 255) ? 0 : code;
writeConfigBlob();
}
async function sendCalibrate() {
if (!chars.command) return;
try { await chars.command.writeValue(new Uint8Array([0x01])); log('Calibration sent — hold still!','warn'); }
@@ -289,9 +331,10 @@ function updateChargeUI() {
// ── Param display ─────────────────────────────────────────────────────────────
function updateDisplay(key, val) {
const map = {
sensitivity: ['valSensitivity', v=>parseFloat(v).toFixed(0)],
deadZone: ['valDeadZone', v=>parseFloat(v).toFixed(3)],
accel: ['valAccel', v=>parseFloat(v).toFixed(2)],
sensitivity: ['valSensitivity', v=>parseFloat(v).toFixed(0)],
deadZone: ['valDeadZone', v=>parseFloat(v).toFixed(3)],
accel: ['valAccel', v=>parseFloat(v).toFixed(2)],
tapThreshold: ['valTapThreshold', v=>(parseFloat(v)*62.5).toFixed(0)+' mg'],
};
const [id,fmt] = map[key];
document.getElementById(id).textContent = fmt(val);
@@ -304,7 +347,7 @@ function setStatus(state) {
pill.className='status-pill '+state;
document.body.className=state;
const cBtn=document.getElementById('connectBtn'), dBtn=document.getElementById('disconnectBtn');
const inputs=document.querySelectorAll('input[type=range],.seg-btn,.toggle input,.cmd-btn');
const inputs=document.querySelectorAll('input[type=range],.seg-btn,.toggle input,.cmd-btn,#tapKeyHex,.mod-btn input');
if (state==='connected') {
cBtn.style.display='none'; dBtn.style.display='';
inputs.forEach(el=>el.disabled=false);