revert: DATA sendCommandWithRetry ke f6a04b8 (direct write+waitForAck, no readPoll)

cad90eb merutekan DATA melalui sendCommand() dengan readPoll state===1
yang selalu true — menyebabkan chunk lambat 5s/kirim saat ACK di-drop.
Kembalikan ke direct writeValueWithResponse + waitForAck.
Selama transfer aktif tidak ada idle gap (Qualcomm >8s), jadi ACK notify
tidak terpengaruh idle-drop.

Perubahan:
- sendCommandWithRetry: build payload sendiri, pakai chunkCRC param,
  write+waitForAck langsung (tidak lewat sendCommand)
- sendCommand: hanya handle INIT dan END (DATA dihapus dari blok)
- Hapus writeSucceeded flag (tidak lagi dipakai)
- Hapus readPoll fallback untuk DATA
- Bump v12 (Footer + SW cache)
This commit is contained in:
a2nr 2026-07-01 06:07:57 +07:00
parent 26ada16c4d
commit 3dd29e1ba1
3 changed files with 35 additions and 28 deletions

View File

@ -5,7 +5,7 @@
<footer class="footer">
<div class="container">
<p>&copy; {env.PUBLIC_COPYRIGHT_TEXT || `${new Date().getFullYear()} Elemes LMS`} <span class="version-badge">v11</span></p>
<p>&copy; {env.PUBLIC_COPYRIGHT_TEXT || `${new Date().getFullYear()} Elemes LMS`} <span class="version-badge">v12</span></p>
{#if $authIsTeacher}
<p><a href="/progress" class="teacher-link">Laporan Progress Siswa</a></p>
{/if}

View File

@ -252,7 +252,6 @@ export class BLEHardwareDeployer {
], 4 + data.length);
const ackTimeout = timeoutMs ?? BLE_TIMEOUT_MS;
let writeSucceeded = false;
console.log(`[BLE-CMD] ${cmdName} idx=${index}: waitForAck timeout=${ackTimeout}ms start`);
const ackPromise = this.waitForAck(index, ackTimeout);
try {
@ -262,7 +261,6 @@ export class BLEHardwareDeployer {
3000,
'writeValueWithResponse'
);
writeSucceeded = true;
console.log(`[BLE-CMD] ${cmdName} idx=${index}: write done, now waiting for ACK...`);
} catch (e) {
console.log(`[BLE-CMD] ${cmdName} idx=${index}: write failed, still waiting for ACK`, e);
@ -356,12 +354,9 @@ export class BLEHardwareDeployer {
}
/* INIT: Firmware sets state to RECEIVING(1) on success poll for it.
* DATA: ACK notify confirms per-chunk CRC. Since state=1 is always
* true during DATA phase, readPoll cannot confirm a specific chunk.
* Instead, readPoll fast-fails on ERROR state (>=5). At deadline, if
* write succeeded (writeValueWithResponse confirms firmware processed
* the chunk), return ok. Otherwise return timeout for retry. */
if (cmd === CMD_INIT || cmd === CMD_DATA) {
* DATA does NOT go through sendCommand (uses sendCommandWithRetry
* with direct write+waitForAck no readPoll). */
if (cmd === CMD_INIT) {
const readPoll = (async () => {
const deadline = Date.now() + ackTimeout;
while (Date.now() < deadline) {
@ -372,39 +367,33 @@ export class BLEHardwareDeployer {
'readValue(state)'
);
const state = dv.getUint8(0);
console.log(`[BLE-CMD] ${cmdName}: readValue state=${state}`);
console.log(`[BLE-CMD] INIT: readValue state=${state}`);
if (state >= 5) {
throw new Error(`Firmware error (state=${state})`);
}
/* INIT: Only RECEIVING(1) means command was processed.
/* Only RECEIVING(1) means INIT was processed.
* Don't accept FLASHING(3) or SERIAL_BRIDGE(4)
* those are stale states from a previous deploy. */
if (cmd === CMD_INIT && state === 1) {
console.log(`[BLE-CMD] ${cmdName}: state=1 (RECEIVING) — INIT confirmed via read poll`);
if (state === 1) {
console.log(`[BLE-CMD] INIT: state=1 (RECEIVING) — INIT confirmed via read poll`);
return 'ok' as const;
}
} catch (e) {
if (!this.isConnected) {
throw new Error('Koneksi BLE terputus');
}
console.log(`[BLE-CMD] ${cmdName}: readValue retry...`, e);
console.log(`[BLE-CMD] INIT: readValue retry...`, e);
}
await new Promise<void>(r => setTimeout(r, 200));
}
/* Deadline reached. For DATA: writeValueWithResponse confirms
* the firmware processed the chunk (GATT write response). */
if (cmd === CMD_DATA && writeSucceeded) {
console.log(`[BLE-CMD] ${cmdName}: deadline, write succeeded — treating as ok`);
return 'ok' as const;
}
console.log(`[BLE-CMD] ${cmdName}: readPoll deadline passed (${ackTimeout}ms)`);
console.log(`[BLE-CMD] INIT: readPoll deadline passed (${ackTimeout}ms)`);
return 'timeout' as const;
})();
const ackWithLabel = ackPromise.then(() => 'ack' as const);
const result = await Promise.race([ackWithLabel, readPoll]);
console.log(`[BLE-CMD] ${cmdName} idx=${index}: resolved via ${result}`);
console.log(`[BLE-CMD] INIT idx=${index}: resolved via ${result}`);
if (result !== 'ack') {
const entry = this.ackResolvers.get(index);
@ -420,13 +409,31 @@ export class BLEHardwareDeployer {
}
}
private async sendCommandWithRetry(cmd: number, index: number, data: Uint8Array, _chunkCRC: number): Promise<void> {
/* Route through sendCommand() which has readValue() state poll
* fallback for DATA (same as INIT/END). The _chunkCRC is kept
* for API compatibility sendCommand computes CRC internally. */
private async sendCommandWithRetry(cmd: number, index: number, data: Uint8Array, chunkCRC: number): Promise<void> {
/* Direct write + waitForAck (no readPoll) reverts cad90eb.
* writeValueWithResponse already confirms delivery via GATT
* response; the ACK notify confirms per-chunk CRC. During
* active data transfer there is no idle gap, so ACK notifies
* are NOT subject to the Qualcomm idle-drop bug */
const payload = new Uint8Array(4 + data.length + 4);
payload[0] = cmd;
payload[1] = index & 0xFF;
payload[2] = (index >> 8) & 0xFF;
payload[3] = data.length;
payload.set(data, 4);
payload.set([
chunkCRC & 0xFF, (chunkCRC >> 8) & 0xFF, (chunkCRC >> 16) & 0xFF, (chunkCRC >> 24) & 0xFF
], 4 + data.length);
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
await this.sendCommand(cmd, index, data);
const ackPromise = this.waitForAck(index);
await this.withTimeout(
this.flashingChar!.writeValueWithResponse(payload),
3000,
'writeValueWithResponse'
);
await ackPromise;
return;
} catch (err) {
if (attempt === MAX_RETRIES - 1) throw err;

View File

@ -1,5 +1,5 @@
// static/sw.js
const CACHE_VERSION = 'elemes-v11';
const CACHE_VERSION = 'elemes-v12';
const STATIC_CACHE = `${CACHE_VERSION}-static`;
const API_CACHE = `${CACHE_VERSION}-api`;
const ASSET_CACHE = `${CACHE_VERSION}-assets`;