Strap usage tracking: accrue real HR-delivering time + battery steps natively
The strap's battery % is coarse/laggy (reads 100% for >24h), so to judge real battery life we accumulate the time it actually delivered HR plus every battery step into strap_uptime.json in the foreground service (persistent across sessions). ble_read_strap_uptime / ble_reset_strap_uptime expose and reset it; Settings shows hours-since-charge + steps with a Charged reset button.
This commit is contained in:
parent
9bc71b19c9
commit
abc4e3cef0
6 changed files with 174 additions and 4 deletions
|
|
@ -5597,6 +5597,7 @@ async function showSessionDetail(sessionId) {
|
|||
<button id="sd-recalc" class="btn-secondary full-width" style="margin-top:8px">Пересчитать</button>
|
||||
<button id="sd-refine" class="btn-secondary full-width" style="margin-top:8px">Уточнить высоты (онлайн)</button>
|
||||
<button id="sd-split" class="btn-secondary full-width" style="margin-top:8px">Разрезать трек</button>
|
||||
<button id="sd-unmerge" class="btn-secondary full-width" style="margin-top:8px;display:none">Разделить склейку</button>
|
||||
<button id="sd-export" class="btn-secondary full-width" style="margin-top:8px">Экспорт GPX</button>
|
||||
<button id="sd-share" class="btn-secondary full-width" style="margin-top:8px">Похвастаться</button>
|
||||
<button id="sd-delete" class="btn-secondary full-width" style="margin-top:8px;color:#cf6679">Удалить тренировку</button>
|
||||
|
|
@ -6387,6 +6388,35 @@ async function showSessionDetail(sessionId) {
|
|||
};
|
||||
}
|
||||
|
||||
// Un-merge button: only for a merged session (points carry >1 orig_session_id). Splits it
|
||||
// back into the original rides, recomputing each; shown only when it's actually a merge.
|
||||
let unmergeBtn = document.querySelector("#sd-unmerge");
|
||||
if (unmergeBtn) (async () => {
|
||||
let n = 0; try { n = await DB.countMergedOrigins(sessionId); } catch {}
|
||||
if (n < 2) return;
|
||||
unmergeBtn.style.display = "";
|
||||
unmergeBtn.textContent = `Разделить склейку (${n})`;
|
||||
unmergeBtn.onclick = async () => {
|
||||
if (!(await appConfirm(`Разделить трек обратно на ${n} исходные поездки? Статистика каждой пересчитается (точную по физике можно потом «Пересчитать» в каждой).`, "Разделить"))) return;
|
||||
unmergeBtn.disabled = true; unmergeBtn.textContent = "Разделяю...";
|
||||
try {
|
||||
let ids = await DB.unmergeGpsSession(sessionId);
|
||||
if (!ids) { showToast("Это не склейка"); unmergeBtn.disabled = false; unmergeBtn.textContent = `Разделить склейку (${n})`; return; }
|
||||
let w = session.rider_weight_kg ?? state.latestWeight?.weight_kg ?? state.profile?.weight_kg ?? 80;
|
||||
for (let id of ids) {
|
||||
let pts = await DB.getGpsPoints(id);
|
||||
if (!pts.length) continue;
|
||||
let st = GPS.computeStats(pts, w, { activityType: session.activity_type });
|
||||
let endT = new Date(pts[pts.length - 1].ts).toTimeString().slice(0, 5);
|
||||
try { await DB.finishGpsSession(id, endT, st.distance, st.kcal, st.avgSpeed, st.maxSpeed, st.elevGain, st.elevLoss, []); } catch (e) { console.error("[unmerge] recompute", e); }
|
||||
}
|
||||
showToast(`Разделено на ${ids.length} трека`);
|
||||
if (winStack.length > 0) winStack.pop().close();
|
||||
_skipPops = 1; history.back(); renderScreen("gps");
|
||||
} catch (e) { showToast("Ошибка: " + (e.message || e)); unmergeBtn.disabled = false; unmergeBtn.textContent = `Разделить склейку (${n})`; }
|
||||
};
|
||||
})();
|
||||
|
||||
// Recalculate button
|
||||
let recalcBtn = document.querySelector("#sd-recalc");
|
||||
if (recalcBtn) recalcBtn.onclick = async () => {
|
||||
|
|
@ -7395,6 +7425,13 @@ async function renderSettings(container) {
|
|||
<div class="settings-item-desc">Не предлагать включить GPS при старте записи трека</div></div>
|
||||
<input id="s-dontask-gps" type="checkbox" ${localStorage.getItem("baldav_dontask_gps") === "1" ? "checked" : ""} style="width:20px;height:20px;">
|
||||
</label>
|
||||
<div class="settings-item" id="s-strap-usage">
|
||||
<div class="settings-item-left">
|
||||
<div class="settings-item-title">Ремень ЧСС: наработка</div>
|
||||
<div class="settings-item-desc" id="s-strap-usage-desc">—</div>
|
||||
</div>
|
||||
<button class="btn-secondary" id="s-strap-reset" style="padding:6px 12px;font-size:13px">Заряжен</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
|
|
@ -7834,6 +7871,33 @@ async function renderSettings(container) {
|
|||
let dontAskGps = container.querySelector("#s-dontask-gps");
|
||||
if (dontAskGps) dontAskGps.onchange = () => { try { localStorage.setItem("baldav_dontask_gps", dontAskGps.checked ? "1" : "0"); } catch {} };
|
||||
|
||||
// Strap usage readout: total HR-delivering time since last "charged" reset + battery steps,
|
||||
// so we can judge real battery life against the claimed 300h (the % reading is coarse/laggy).
|
||||
let strapDesc = container.querySelector("#s-strap-usage-desc");
|
||||
let fillStrapUsage = async () => {
|
||||
if (!strapDesc) return;
|
||||
try {
|
||||
let raw = await window.__TAURI__.core.invoke("ble_read_strap_uptime");
|
||||
let u = JSON.parse(raw || "{}");
|
||||
let sec = Math.round(u.active_sec || 0);
|
||||
let h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60);
|
||||
let bat = (u.bat != null) ? `${u.bat}%` : "?";
|
||||
let steps = Array.isArray(u.steps) ? u.steps : [];
|
||||
let since = u.since ? new Date(u.since * 1000).toLocaleDateString("ru-RU") : "—";
|
||||
let stepStr = steps.length
|
||||
? " · шаги: " + steps.map(s => `${s.bat}%`).join("->")
|
||||
: " · шагов заряда нет (ещё 100%?)";
|
||||
strapDesc.textContent = `заряд ${bat} · в работе ${h}ч ${m}м с ${since}${stepStr}`;
|
||||
} catch (e) { strapDesc.textContent = "нет данных"; }
|
||||
};
|
||||
fillStrapUsage();
|
||||
let strapReset = container.querySelector("#s-strap-reset");
|
||||
if (strapReset) strapReset.onclick = async () => {
|
||||
if (!(await appConfirm("Сбросить счётчик наработки ремня (отметить как заряженный)?", "Сбросить"))) return;
|
||||
try { await window.__TAURI__.core.invoke("ble_reset_strap_uptime"); } catch {}
|
||||
fillStrapUsage();
|
||||
};
|
||||
|
||||
let bleScanBtn = container.querySelector("#s-ble-scan");
|
||||
let bleResults = container.querySelector("#s-ble-results");
|
||||
if (bleScanBtn) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue