diff --git a/docs/session-logs/2026-10-30_barometer-ble-baldav.md b/docs/session-logs/2026-10-30_barometer-ble-baldav.md index ab14fd0..ce991ec 100644 --- a/docs/session-logs/2026-10-30_barometer-ble-baldav.md +++ b/docs/session-logs/2026-10-30_barometer-ble-baldav.md @@ -22,6 +22,27 @@ ## Состояние Проверено компиляцией (сборка 0.1.538, без ошибок). На устройстве НЕ проверено - нет железа. +## Продолжение того же дня: обвязка, UI, проверка эмулятором + +Железа ещё нет, поэтому эмулировали датчик на ПК (`tools/baro-sensor/emulate_baro.py` - +BlueZ D-Bus GATT-сервер, вещает ESS `0x181A` с давлением/температурой), телефон читал его +реальным путём. Проверено на устройстве: `BleHR: HR:70` и `BleBaro: Baro:... Pa` одновременно. + +Сделано: +- `BleBaroService` - отдельный сервис/соединение для барометра, работает параллельно с + пульсометром (один адаптер держит несколько LE-связей). Баро-подключения маршрутизируются + в него из `MainActivity`. Авто-подключение при старте: баро из `baro_device.json`, пульс из + `ble_device.json` (оба сами цепляются). +- Rust: `ble_baro_disconnect`, `ble_save_baro_device`. JS сохраняет baro-устройство при привязке. +- Настройки: секция "Датчики (BLE)" - пульсометр и барометр раздельно, свои слоты и кнопки, + живой показ давления/температуры/высоты. +- Трекинг: `altitude_baro_m` в точках, предпочитается в статистике высот (null без датчика). +- Экран записи: "Высота" от барометра (обновление 1 Гц, пометка точкой), температура с датчика + (белая, с десятой). +- Починки по пути: directConnect при таймауте скана; Locale.US в JSON (была запятая от RU-локали); + маршрутизация парсинга по характеристике (баро не писал в пульс); навигация после Стоп в VR/ЧСС + (на список, а не в детали). + ## Осталось (по приходу платы) - Активировать модуль: `BARO.start()` при подключении baro-устройства, задать `BARO.getGpsAlt`. - Привязать высоту барометра к треку поездки (в точки / поправка высот П.5). diff --git a/frontend/css/styles.css b/frontend/css/styles.css index 276915a..e573c76 100644 --- a/frontend/css/styles.css +++ b/frontend/css/styles.css @@ -1460,7 +1460,7 @@ body.dark-map .poi-badge { background: #111; border-color: #eee; } background: var(--surface); } .rec-topbar .rec-top { font-size: 27px; font-weight: 700; color: var(--on-surface); } -.rec-topbar .rec-top-mid { color: var(--muted); } +.rec-topbar .rec-top-mid { color: var(--on-surface); } .rec-lbl { font-size: 10px; diff --git a/frontend/js/db.js b/frontend/js/db.js index eda3781..2925738 100644 --- a/frontend/js/db.js +++ b/frontend/js/db.js @@ -745,6 +745,7 @@ async function addGpsPoint(sessionId, ts, lat, lon, altGps, speedMs, seq, bleDat ["accel_rms", sensorData?.accel_rms], ["accel_max", sensorData?.accel_max], ["gyro_pitch", sensorData?.gyro_pitch], ["gyro_yaw", sensorData?.gyro_yaw], ["bearing_deg", sensorData?.bearing], ["accuracy_m", sensorData?.accuracy], + ["altitude_baro_m", sensorData?.altitude_baro_m], ]; for (const [col, val] of extra) { if (_gpsPointCols.has(col)) { cols.push(col); vals.push(val ?? null); } @@ -1676,6 +1677,7 @@ async function runMigrations() { if (!gpsCols.includes("gyro_yaw")) await execute("ALTER TABLE gps_points ADD COLUMN gyro_yaw REAL"); if (!gpsCols.includes("bearing_deg")) await execute("ALTER TABLE gps_points ADD COLUMN bearing_deg REAL"); if (!gpsCols.includes("accuracy_m")) await execute("ALTER TABLE gps_points ADD COLUMN accuracy_m REAL"); + if (!gpsCols.includes("altitude_baro_m")) await execute("ALTER TABLE gps_points ADD COLUMN altitude_baro_m REAL"); // barometric altitude from the BLE sensor (null without it) if (!gpsCols.includes("lat_smoothed")) await execute("ALTER TABLE gps_points ADD COLUMN lat_smoothed REAL"); if (!gpsCols.includes("lon_smoothed")) await execute("ALTER TABLE gps_points ADD COLUMN lon_smoothed REAL"); if (!gpsCols.includes("estimated")) await execute("ALTER TABLE gps_points ADD COLUMN estimated INTEGER DEFAULT 0"); // gap-fill (BRouter) points, reversible diff --git a/frontend/js/gps.js b/frontend/js/gps.js index 9d9a288..ba79d19 100644 --- a/frontend/js/gps.js +++ b/frontend/js/gps.js @@ -311,7 +311,8 @@ GPS.computeStats = function(points, weightKg, opts) { // e.g. [45, 45, 52, 45, 45] — the 52 is a GPS altitude glitch. // Prefer terrain (SRTM/open-elevation) altitude when available — GPS altitude is noisy // and the stuck-filter destroys its real signal. Terrain is clean and accurate for slope. - const rawAlt = sorted.map(p => p.altitude_srtm_m ?? p.altitude ?? p.altitude_gps_m ?? null); + // Prefer the barometer (best for real climbs/descents) when present, then terrain (SRTM), then GPS. + const rawAlt = sorted.map(p => p.altitude_baro_m ?? p.altitude_srtm_m ?? p.altitude ?? p.altitude_gps_m ?? null); const filteredAlt = rawAlt.slice(); for (let i = 1; i < filteredAlt.length - 1; i++) { if (filteredAlt[i] == null) continue; @@ -1170,6 +1171,15 @@ GPS.recoverFromFiles = async function(onProgress) { // live marker — the same fix is never written twice with different timestamps. GPS._processIncomingPoint = async function(p, fromNative) { if (!GPS.recording.active) return; + + // Attach barometric altitude to this point when the BLE baro sensor is live and anchored. + // Null without a barometer, so elevation stats fall back to GPS/SRTM exactly as before. + try { + if (window.BARO && BARO.isLive()) { + let ba = BARO.altitude(); + if (ba != null && isFinite(ba)) p.altitude_baro_m = ba; + } + } catch (e) {} // DB writes come only from the source of truth: native points on Android, or // watchPosition on desktop (no native service). const writeDb = !!fromNative || !GPS.recording.nativeService; diff --git a/frontend/js/main.js b/frontend/js/main.js index f40942e..4cee2f4 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -4871,8 +4871,8 @@ function renderWorkoutInline(container) { let res = await GPS.stopRecording(); if (res && res.sessionId) { showToast(`Зарядка: ${res.kcal} ккал, ${GPS.formatDuration(res.durationSec)}, пульс ${res.avgHr ?? "--"}/${res.maxHr ?? "--"}`); - showSessionDetail(res.sessionId); - } else { renderScreen("gps"); } + } + renderScreen("gps"); // back to the tracks list after stop, like a GPS ride (not into details) }; } @@ -5470,9 +5470,10 @@ async function renderRecordingInline(container) { let median = sorted[Math.floor(sorted.length / 2)] || 0; if (spdEl) spdEl.textContent = median.toFixed(1); - // Elevation - let alt = p.altitude; - if (alt != null && elevEl) elevEl.textContent = Math.round(alt) + " м"; + // Elevation - prefer the live barometer (smoother, tracks real climbs) over noisy GPS altitude + let baroAlt = (window.BARO && BARO.isLive()) ? BARO.altitude() : null; + let alt = (baroAlt != null) ? baroAlt : p.altitude; + if (alt != null && elevEl) elevEl.textContent = Math.round(alt) + (baroAlt != null ? " м·" : " м"); // Pause indicator if (GPS.recording.pauseStart) { @@ -5506,6 +5507,14 @@ async function renderRecordingInline(container) { // Update clock let clockEl = document.querySelector("#rec-clock"); if (clockEl) clockEl.textContent = new Date().toTimeString().slice(0, 5); + // Barometer readout at 1 Hz, independent of GPS fixes (so altitude/temp move smoothly). + if (window.BARO && BARO.isLive()) { + let ba = BARO.altitude(); + if (ba != null && elevEl) elevEl.textContent = Math.round(ba) + " м·"; // trailing dot = from the barometer + let tc = BARO.state ? BARO.state.lastTempC : null; + let tEl = document.querySelector("#rec-temp"); + if (tc != null && tEl) tEl.textContent = tc.toFixed(1) + "°"; // one decimal: visibly live + has the dot + } }, 1000); // Fetch temperature — try multiple APIs (mobile internet blocks foreign services) @@ -7806,18 +7815,34 @@ async function renderSettings(container) {
-
Пульсометр (BLE)
+
Датчики (BLE)
-
${state.profile.ble_device_name || "Не привязан"}
-
${state.profile.ble_device_addr ? state.profile.ble_device_addr : "Нажмите для поиска"}
+
Пульсометр: ${state.profile.ble_device_name || "не привязан"}
+
${state.profile.ble_device_addr ? state.profile.ble_device_addr : "найдите в поиске ниже"}
${state.profile.ble_device_addr ? `` : ""} ${state.profile.ble_device_addr ? `` : ""} -
+
+
+
Барометр высоты: ${state.profile.baro_device_name || "не привязан"}
+
${state.profile.baro_device_addr ? state.profile.baro_device_addr : "найдите в поиске ниже"}
+
+
+ ${state.profile.baro_device_addr ? `` : ""} + ${state.profile.baro_device_addr ? `` : ""} +
+
+
+
+
Поиск устройств
+
Пульсометр и барометр привяжутся каждый в свою строку. Пока работает одно BLE-подключение за раз.
+
+ +