Add configuration slider for double tapping
This commit is contained in:
+56
-13
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user