Workout HR: log samples natively so recording survives screen-off
The JS 2s poll that stored workout HR samples is frozen by Android when the screen is off, so a sleep-long recording kept only the on-screen stretches (the strap and live display were fine — the persist path was the gap). The foreground service now appends every reading to ble_hr_log.jsonl; ble_read_hr_log exposes it; on stop the workout rebuilds its samples from that log (falling back to the poll array). ble_clear_hr truncates the log at start.
This commit is contained in:
parent
85d5e47972
commit
ee47af4479
6 changed files with 98 additions and 5 deletions
|
|
@ -881,6 +881,33 @@ GPS._stopHrMonitor = function() {
|
|||
try { window.__TAURI__.core.invoke("ble_clear_hr"); } catch {}
|
||||
};
|
||||
|
||||
// Rebuild the workout's HR series from the NATIVE log (ble_hr_log.jsonl), which the
|
||||
// foreground service appends to even with the screen off — unlike the JS poll, which
|
||||
// Android freezes in the background (so a sleep-long workout kept only the on-screen bits).
|
||||
// Returns [[relSec, bpm], …] relative to the recording start, sorted and de-duplicated,
|
||||
// or [] if the log is empty/unavailable.
|
||||
GPS._readHrLogSamples = async function() {
|
||||
if (!GPS.recording.startTime) return [];
|
||||
let raw = "";
|
||||
try { raw = await window.__TAURI__.core.invoke("ble_read_hr_log"); } catch { return []; }
|
||||
if (!raw) return [];
|
||||
const startSec = GPS.recording.startTime.getTime() / 1000;
|
||||
const out = [];
|
||||
let lastRel = -1;
|
||||
for (const line of raw.split("\n")) {
|
||||
const s = line.trim();
|
||||
if (!s) continue;
|
||||
let o; try { o = JSON.parse(s); } catch { continue; } // skip a torn last line
|
||||
if (!o || !(o.hr > 0) || o.ts == null) continue;
|
||||
const rel = Math.round(o.ts - startSec);
|
||||
if (rel < 0 || rel === lastRel) continue; // before start, or same-second dup
|
||||
out.push([rel, o.hr]);
|
||||
lastRel = rel;
|
||||
}
|
||||
out.sort((a, b) => a[0] - b[0]);
|
||||
return out;
|
||||
};
|
||||
|
||||
// Turn HR on/off during a recording (the toggle on the record screen). ON connects to the
|
||||
// paired strap (Kotlin scans ~5s and stops cleanly if it's absent — so no constant scanning /
|
||||
// drain when the strap isn't there) and polls; OFF stops the poll and disconnects.
|
||||
|
|
@ -1550,7 +1577,13 @@ GPS.stopRecording = async function() {
|
|||
|
||||
// HR-only workout: no GPS to stop. Compute net kcal from HR, save samples, finalise.
|
||||
if (GPS.recording.activityType === "workout") {
|
||||
const samples = GPS.recording.hrSamples || [];
|
||||
// Prefer the native HR log (complete through screen-off) over the JS poll's in-memory
|
||||
// array (frozen in the background). Fall back to the poll array if the log is empty.
|
||||
let samples = GPS.recording.hrSamples || [];
|
||||
try {
|
||||
const native = await GPS._readHrLogSamples();
|
||||
if (native.length > samples.length) samples = native;
|
||||
} catch (e) { console.error("[workout] native hr log read", e); }
|
||||
const kcal = GPS.hrKcalNet(samples, GPS.recording.hrProfile);
|
||||
const durationSec = GPS.recording.startTime ? Math.round((Date.now() - GPS.recording.startTime.getTime()) / 1000) : 0;
|
||||
const hrs = samples.map(s => s[1]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue