fixed most upload issues, updated schematic
This commit is contained in:
+38
-18
@@ -34,9 +34,9 @@ const TAG_DELETE = 0xd0;
|
||||
|
||||
export const MAX_TRACKS = 32;
|
||||
|
||||
// Keep below ATT MTU - 3. The firmware calls configPrphBandwidth(BANDWIDTH_MAX)
|
||||
// so Chrome typically negotiates MTU 247 (244-byte payload). 180 stays well clear.
|
||||
const DATA_CHUNK = 180;
|
||||
// ATT MTU is negotiated to 247 (firmware requests via requestMtuExchange(247)).
|
||||
// Write-without-response payload = MTU - 3 = 244 bytes.
|
||||
const DATA_CHUNK = 244;
|
||||
|
||||
export function isSupported() {
|
||||
return typeof navigator !== 'undefined' && !!navigator.bluetooth;
|
||||
@@ -231,40 +231,60 @@ export class BabyMobile extends EventTarget {
|
||||
});
|
||||
}
|
||||
|
||||
// Stream a complete WAV file to a slot. `onProgress(sent, total)` is called as
|
||||
// the firmware acknowledges bytes via the status characteristic.
|
||||
// Stream a complete WAV file to a slot. `onProgress(shown, total)` reports a
|
||||
// single monotonic value combining the locally-sent count and the device's
|
||||
// acknowledged count.
|
||||
async upload(track, wavBytes, onProgress) {
|
||||
this._requireConnected();
|
||||
const total = wavBytes.byteLength;
|
||||
this._statBytes = 0;
|
||||
let acked = 0;
|
||||
|
||||
const progressHandler = (ev) => {
|
||||
acked = ev.detail.bytes;
|
||||
if (onProgress) onProgress(Math.min(acked, total), total);
|
||||
// The optimistic local "sent" count races ahead of the device's ack
|
||||
// notifications, and the two arrive interleaved (and acks can lag badly over
|
||||
// a slow mobile link). Reporting both directly makes the bar jump forward then
|
||||
// snap backward. Clamp to a high-water mark so progress only ever advances.
|
||||
let shown = 0;
|
||||
const report = (n) => {
|
||||
const v = Math.min(Math.max(0, n), total);
|
||||
if (v > shown) { shown = v; if (onProgress) onProgress(shown, total); }
|
||||
};
|
||||
|
||||
const progressHandler = (ev) => { acked = ev.detail.bytes; report(acked); };
|
||||
this.addEventListener('progress', progressHandler);
|
||||
|
||||
try {
|
||||
this._log(`Starting upload of ${total} bytes to slot ${track}…`);
|
||||
await this._writeCmd([CMD_UPLOAD_START, track & 0xff]);
|
||||
|
||||
// Send all data chunks. Progress is tracked client-side (bytes sent).
|
||||
// The firmware queues bytes into a ring buffer; actual flash writes happen
|
||||
// in the background. We do NOT poll per-packet notifications here — that
|
||||
// caused BLE notification-buffer overflow for large files.
|
||||
for (let off = 0; off < total; off += DATA_CHUNK) {
|
||||
const chunk = wavBytes.subarray(off, Math.min(off + DATA_CHUNK, total));
|
||||
await this.data.writeValueWithoutResponse(chunk);
|
||||
if (onProgress) onProgress(off + chunk.length, total);
|
||||
// Send all data chunks in pipelined batches. Chrome on Windows serialises
|
||||
// individually-awaited writeValueWithoutResponse calls (one Promise per OS
|
||||
// event loop tick), which limits throughput to ~one packet per ~50 ms and
|
||||
// turns a 1.4 MB upload into a 6-minute ordeal. Firing PIPE writes before
|
||||
// awaiting them all fills multiple BLE connection events per await, saturating
|
||||
// the link. The firmware's 32 KB ring buffer absorbs bursts comfortably.
|
||||
const PIPE = 6; // packets in flight before each await (~1 KB per batch)
|
||||
for (let off = 0; off < total; off += DATA_CHUNK * PIPE) {
|
||||
const batch = [];
|
||||
for (let i = 0; i < PIPE && off + i * DATA_CHUNK < total; i++) {
|
||||
const s = off + i * DATA_CHUNK;
|
||||
batch.push(this.data.writeValueWithoutResponse(
|
||||
wavBytes.subarray(s, Math.min(s + DATA_CHUNK, total))));
|
||||
}
|
||||
await Promise.all(batch);
|
||||
// Cap the optimistic local count at 99%; the final 1% is filled only when
|
||||
// the device's finalize ack confirms the bytes actually landed in flash.
|
||||
report(Math.min(off + DATA_CHUNK * PIPE, Math.floor(total * 0.99)));
|
||||
}
|
||||
|
||||
// Tell the firmware we're done. It will drain its ring buffer, write the
|
||||
// track table, then send ONE finalize notification with the total byte count.
|
||||
await this._writeCmd([CMD_UPLOAD_END]);
|
||||
|
||||
// Wait for that single finalize notification (up to 20 s to allow flash writes).
|
||||
await this._waitForAck(total, 20000);
|
||||
// Wait for that single finalize notification. Allow up to 60 s: at 300 ms/sector
|
||||
// worst-case, a 1.4 MB file has ~350 sectors = 105 s of erase time. In practice
|
||||
// most sectors erase in ~30 ms so finalization completes in well under 60 s.
|
||||
await this._waitForAck(total, 60000);
|
||||
const ok = acked >= total;
|
||||
if (ok) {
|
||||
this._log(`✅ Upload complete: ${acked}/${total} bytes acknowledged.`);
|
||||
|
||||
Reference in New Issue
Block a user