// Barometric altitude from the DIY BLE sensor "BaroBalDav" (ESP32-C3 + BMP280). // PASSIVE module: it only defines window.BARO. Nothing runs until BARO.start() is called, so with // no barometer connected the app behaves exactly as before. Activation (start on a "baro" connect, // plus the getGpsAlt hook) is wired in main.js only when a baro device is in use. // // How it works: the Kotlin BLE service writes the latest {pressure_pa, temp_c, ts} to a file; the // Rust command `ble_get_baro` returns it. We turn pressure into altitude with the hypsometric // formula using the sensor's own temperature, and anchor the absolute value to the GPS altitude at // ride start. The sensor is excellent for relative change (~1 m); weather drift of the absolute // baseline is handled by the GPS anchor (optionally re-anchored slowly). let BARO = {}; (function () { let R = 287.05; // specific gas constant for dry air, J/(kg*K) let G = 9.80665; // standard gravity, m/s^2 let state = { anchorPa: null, // reference pressure at the anchor point anchorAltM: null, // absolute altitude (m) at the anchor point, from GPS lastPa: null, // most recent pressure, Pa lastTempC: null, // most recent chip temperature, C (for the hypsometric term) lastTs: 0, // unix seconds of the last reading lastAltM: null, // computed absolute altitude at the last reading polling: false, timer: null, }; BARO.state = state; // Height of a level at pressure `pa` above a level at pressure `refPa`, at air temperature tC. // Hypsometric formula: dh = (R*T/g) * ln(refPa/pa). Measured temperature is used instead of a // 15 C constant, so a hot or cold day does not bias the climb. Returns metres (positive = higher). function deltaH(refPa, pa, tC) { let T = (tC == null ? 15 : tC) + 273.15; return (R * T / G) * Math.log(refPa / pa); } BARO.deltaH = deltaH; // Pin the barometric altitude to a known absolute altitude (from GPS) at a given pressure. // Call once at ride start; may be re-called later to slowly correct weather drift. BARO.anchor = function (gpsAltM, pa) { if (pa == null) pa = state.lastPa; if (gpsAltM == null || pa == null) return false; state.anchorAltM = gpsAltM; state.anchorPa = pa; state.lastAltM = BARO.altitude(); return true; }; // Absolute altitude now = anchor altitude + barometric rise since the anchor. null until anchored. BARO.altitude = function () { if (state.anchorPa == null || state.lastPa == null) return null; return state.anchorAltM + deltaH(state.anchorPa, state.lastPa, state.lastTempC); }; // Fresh reading within 15 s? BARO.isLive = function () { return state.lastTs > 0 && (Date.now() / 1000 - state.lastTs) < 15; }; // Override this to provide the current GPS altitude (m) for auto-anchoring; return null if no fix. // Default: the altitude of the last recorded GPS point, if a recording is active. BARO.getGpsAlt = function () { try { let pts = window.GPS && GPS.recording && GPS.recording.points; if (pts && pts.length) { let p = pts[pts.length - 1]; let a = p.altitude_srtm_m != null ? p.altitude_srtm_m : p.altitude; return a != null ? a : null; } } catch (e) {} return null; }; async function tick() { let r = null; try { r = await window.__TAURI__.core.invoke("ble_get_baro"); } catch (e) {} if (r && r.pressure_pa > 0) { state.lastPa = r.pressure_pa; if (r.temp_c != null) state.lastTempC = r.temp_c; state.lastTs = r.timestamp || Math.floor(Date.now() / 1000); // auto-anchor on the first good reading, once GPS has an altitude to anchor to if (state.anchorPa == null) { let g = null; try { g = BARO.getGpsAlt(); } catch (e) {} if (g != null) BARO.anchor(g, state.lastPa); } state.lastAltM = BARO.altitude(); } } BARO.tick = tick; // Begin polling the sensor (~1 Hz). No-op if already running. Call this only when a baro device // is connected, so the app does nothing extra when no barometer is in use. BARO.start = function () { if (state.polling) return; state.polling = true; let loop = async function () { if (!state.polling) return; await tick(); if (state.polling) state.timer = setTimeout(loop, 1000); // 1 s poll interval (a real delay) }; loop(); }; BARO.stop = function () { state.polling = false; if (state.timer) { clearTimeout(state.timer); state.timer = null; } }; // Clear the anchor and readings (e.g. between rides) so the next ride re-anchors fresh. BARO.reset = function () { state.anchorPa = null; state.anchorAltM = null; state.lastPa = null; state.lastTempC = null; state.lastTs = 0; state.lastAltM = null; }; window.BARO = BARO; })();