HR strap battery + low-charge voice/beep alerts at 10% and 5%
- BleHrService reads the standard Battery service (0x180F/0x2A19) 2.5s after the HR subscribe and every 60s; writes 'bat' into ble_hr_data.json. - HrReading gains battery: Option<u8> (serde alias 'bat'). - gps.js: the HR poll captures reading.battery; _checkBatteryAlert speaks (speechSynthesis ru-RU) and beeps once at 10% and 5% (beep is the guaranteed cue where TTS is absent), resetting above 15%. - Workout screen shows the strap charge in its status line.
This commit is contained in:
parent
f704498d11
commit
62434d6155
5 changed files with 95 additions and 4 deletions
|
|
@ -840,6 +840,7 @@ GPS._startHrMonitor = async function() {
|
|||
GPS.recording.hrPollInterval = setInterval(async () => {
|
||||
try {
|
||||
const reading = await window.__TAURI__.core.invoke("ble_get_hr");
|
||||
if (reading && reading.battery != null) { GPS.recording.currentBattery = reading.battery; GPS._checkBatteryAlert(reading.battery); }
|
||||
if (reading && reading.hr > 0) {
|
||||
GPS.recording.currentHr = reading.hr;
|
||||
GPS.recording.currentBleData = reading;
|
||||
|
|
@ -867,6 +868,47 @@ GPS._stopHrMonitor = function() {
|
|||
try { window.__TAURI__.core.invoke("ble_clear_hr"); } catch {}
|
||||
};
|
||||
|
||||
// Low-battery warning for the HR strap: announce (voice) and beep ONCE at 10% and 5%.
|
||||
// Resets when the strap reads healthy again (recharged / battery replaced).
|
||||
GPS._batAlerted = {};
|
||||
GPS._checkBatteryAlert = function(bat) {
|
||||
if (bat == null || bat < 0 || bat > 100) return;
|
||||
if (bat > 15) { GPS._batAlerted = {}; return; }
|
||||
for (let thr of [10, 5]) {
|
||||
if (bat <= thr && !GPS._batAlerted[thr]) {
|
||||
GPS._batAlerted[thr] = true;
|
||||
GPS._speakOrBeep(`Заряд пульсометра ${thr} процентов`);
|
||||
}
|
||||
}
|
||||
};
|
||||
GPS._speakOrBeep = function(text) {
|
||||
let spoke = false;
|
||||
try {
|
||||
if (window.speechSynthesis && typeof SpeechSynthesisUtterance !== "undefined") {
|
||||
let u = new SpeechSynthesisUtterance(text);
|
||||
u.lang = "ru-RU"; u.rate = 0.95;
|
||||
window.speechSynthesis.speak(u);
|
||||
spoke = true;
|
||||
}
|
||||
} catch {}
|
||||
// Always beep too (TTS may be absent on a de-Googled ROM; the beep is the guaranteed cue).
|
||||
try { GPS._beep(spoke ? 1 : 3); } catch {}
|
||||
};
|
||||
GPS._beep = function(times) {
|
||||
times = times || 1;
|
||||
let ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
let one = (i) => {
|
||||
let o = ctx.createOscillator(), g = ctx.createGain();
|
||||
o.frequency.value = 880; o.type = "sine";
|
||||
o.connect(g); g.connect(ctx.destination);
|
||||
g.gain.value = 0.25;
|
||||
let t0 = ctx.currentTime + i * 0.35;
|
||||
o.start(t0); o.stop(t0 + 0.2);
|
||||
};
|
||||
for (let i = 0; i < times; i++) one(i);
|
||||
setTimeout(() => { try { ctx.close(); } catch {} }, times * 350 + 400);
|
||||
};
|
||||
|
||||
// Read points from native service file
|
||||
// Parse raw JSONL (one native point per line) into sorted point objects.
|
||||
GPS._parseNativePoints = function(raw) {
|
||||
|
|
|
|||
|
|
@ -4514,6 +4514,10 @@ function renderWorkoutInline(container) {
|
|||
if (avgEl) avgEl.textContent = hrs.length ? Math.round(hrs.reduce((a, b) => a + b, 0) / hrs.length) : "--";
|
||||
if (maxEl) maxEl.textContent = hrs.length ? Math.max(...hrs) : "--";
|
||||
if (GPS.recording.currentHr && hrEl) hrEl.textContent = GPS.recording.currentHr;
|
||||
if (statusEl && GPS.recording.currentHr) {
|
||||
let b = GPS.recording.currentBattery;
|
||||
statusEl.textContent = "Пульсометр на связи" + (b != null ? ` · заряд ${b}%` : "");
|
||||
}
|
||||
};
|
||||
clearInterval(window._wkTimer);
|
||||
window._wkTimer = setInterval(tick, 1000);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue