diff --git a/PROJECT_MAP.md b/PROJECT_MAP.md index 828f44f..e968f4d 100644 --- a/PROJECT_MAP.md +++ b/PROJECT_MAP.md @@ -23,7 +23,7 @@ |------|-------|------------| | `js/main.js` | ~5800 | Экраны, навигация, модалки, графики, нутриенты, HR UI, компас (EMA + gimbal lock), ландшафт (3 режима), дороги | | `js/db.js` | ~1500 | SQLite через Tauri IPC. Миграции, CRUD, 41 поле добавок, water_pct, reserves, sync | -| `js/gps.js` | ~1200 | GPS запись, физика ккал (power model), altitude filter, BLE HR polling, dead reckoning | +| `js/gps.js` | ~1400 | GPS запись, физика ккал (power model), altitude filter, BLE HR polling, dead reckoning. Импорт нативных точек: JS-поллинг (экран-gated) + ПОЛНЫЙ до-импорт по множеству ts на «Стоп»/recoverSession (без потерь при экране-офф), отбор по точности (minAccuracyM), bearing_deg | | `js/sync.js` | ~300 | Синхронизация между устройствами (LAN, PHP сервер на роутере) | | `js/tilecache.js` | 178 | Оффлайн кэш тайлов карты (IndexedDB) | | `js/bmr.js` | ~70 | BMR (Mifflin-St Jeor), РСП из БД (33 нутриента), полураспад | @@ -101,7 +101,7 @@ | `supplements` | Добавки (БАД) | name, daily_dose_g, витамины/минералы | | `supplement_log` | Приём добавок | date, supplement_id, dose_g | | `gps_sessions` | GPS тренировки | date, activity_type, distance, kcal, speed, elevation, bike_id | -| `gps_points` | Точки трека | session_id, ts, lat, lon, altitude, speed, seq, heart_rate, spo2, bp_sys, bp_dia | +| `gps_points` | Точки трека | session_id, ts, lat, lon, altitude_gps_m, altitude_srtm_m, speed_ms, seq, bearing_deg, heart_rate, spo2, bp_sys, bp_dia, accel_rms/max, gyro_pitch/yaw | | `bikes` | Велосипеды | name, weight_kg, wheel, звёзды, surface_type, is_active | | `rdi_standards` | Стандарты РСП (RU/EU/US) | code, name | | `rdi_values` | Нормы нутриентов | standard_id, nutrient_key, value, norm_type (rdi/aup) | @@ -129,7 +129,7 @@ ### Экраны - **Сегодня** — баланс ккал, еда (продукты + блюда), GPS тренировки, активность, вес, сон, добавки, прогресс-бар жидкости, нутриенты (приход + запасы) - **Продукты** — подтабы [Продукты | Рецепты], поиск, карточки с КБЖУ, добавление в дневник -- **Трек** — GPS тренировки (запись с HR), карта с компасом (leaflet-rotate), ландшафтный режим (CSS rotation), графики скорости/высоты/ЧСС, импорт GPX/FTB, dead reckoning, placeholder до GPS, карта дорог (дедуп 2м, вело/пешие) +- **Трек** — GPS тренировки (запись с HR), карта с компасом (leaflet-rotate), course-up по GPS-курсу (карта и панель доворачиваются в ландшафт вместе, `bearing = h`), ландшафтный режим (CSS rotation), графики скорости/высоты/ЧСС, импорт GPX/FTB, dead reckoning, placeholder до GPS, карта дорог (дедуп 2м, вело/пешие) - **Добавки** — 41 нутриент (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10) - **Настройки** — профиль, образ жизни, нормы ЧСС/давления, BLE пульсометр, велосипеды, GPS конфиг, OTA обновления, импорт тайлов, карта дорог diff --git a/README.md b/README.md index fe4b2ba..aaa2eec 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## Возможности - **Дневник питания** — 24400+ продуктов (Магнит, 5ка, Health-Diet.ru, Ашан, Макдоналдс, KFC, Додо Пицца, KDV, ИОН, Гриднев). 37 нутриентов (витамины, минералы, omega-3/6, вода, клетчатка). Рецепты с термообработкой. Формулы порций (4*30, 300+50). -- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Отбор точек по точности (жёсткий порог по настройке «Мин. точность» — рубит грубые сотовые фиксы ~800м, оставляет слабый но точный GPS; где сигнала нет — честный разрыв). Направление (bearing) каждой точки пишется в БД. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Импорт тайлов по регионам (SQLite, роутер LAN). +- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Отбор точек по точности (жёсткий порог по настройке «Мин. точность» — рубит грубые сотовые фиксы ~800м, оставляет слабый но точный GPS; где сигнала нет — честный разрыв). Направление (bearing) каждой точки пишется в БД. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация) и course-up по GPS-курсу (в ландшафте карта доворачивается вместе с экраном — курс к физическому верху). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Импорт тайлов по регионам (SQLite, роутер LAN). - **BLE пульсометр** — подключение к FitPro браслетам и стандартным HR датчикам (0x180D). ЧСС, SpO2, давление в реальном времени при записи трека. - **Расчёт ккал** — физическая модель для велосипеда (мощность из gradient + Crr + CdA). MET-таблица для ходьбы/бега и тренировок (приседания 6.0, скакалка 12.3, растяжки 2.3, бассейн 8.0). Профили велосипедов. BMR по Mifflin-St Jeor с коэффициентом активности. - **Тренировки** — приседания (подходы x повторения), скакалка, растяжки, бассейн (дистанция). Автоматический расчёт ккал по MET и весу. diff --git a/frontend/js/gps.js b/frontend/js/gps.js index cc82b13..ac2a6d0 100644 --- a/frontend/js/gps.js +++ b/frontend/js/gps.js @@ -706,16 +706,19 @@ GPS.recoverSession = async function() { try { const s = JSON.parse(saved); if (!s.sessionId) { GPS._clearPersistedState(); return; } - // Import remaining native points not yet in DB (idempotent by timestamp) + // Import EVERY native point missing from the DB, deduped by the SET of existing + // timestamps — NOT "ts > maxDbTs". The live poll is screen-gated and leaves + // sparse points spread across the whole session, so the max DB ts can be the + // LAST point; "ts > maxDbTs" would then import nothing and the gaps stay. const existingPts = await DB.getGpsPoints(s.sessionId); - const maxDbTs = existingPts.length ? Math.max(...existingPts.map(p => new Date(p.ts).getTime())) : 0; + const dbTs = new Set(existingPts.map(p => new Date(p.ts).getTime())); const nativePoints = await GPS._readNativePoints(); let imported = 0; let seq = existingPts.length; for (const p of nativePoints) { - if (new Date(p.ts).getTime() <= maxDbTs) continue; + if (dbTs.has(new Date(p.ts).getTime())) continue; seq++; - await DB.addGpsPoint(s.sessionId, p.ts, p.lat, p.lon, p.altitude, p.speed_ms, seq); + await DB.addGpsPoint(s.sessionId, p.ts, p.lat, p.lon, p.altitude, p.speed_ms, seq, null, p); imported++; } // Finalize the session @@ -801,7 +804,10 @@ GPS.startRecording = async function(activityType, weightKg) { GPS.recording._lastProcessedTs = pTs; newCount++; if (GPS.recording.onImportPoint) GPS.recording.onImportPoint(p); - if (newCount % 20 === 0) await new Promise(r => requestAnimationFrame(r)); + // Yield a frame so the growing track repaints — but ONLY when visible. + // requestAnimationFrame never fires while the screen is off (page hidden), + // so awaiting it there would hang this loop forever and freeze the import. + if (newCount % 20 === 0 && !document.hidden) await new Promise(r => requestAnimationFrame(r)); } GPS.recording.onPoint = origOnPoint; // Final sync — marker, pan, course and stats from the full point set. diff --git a/frontend/js/main.js b/frontend/js/main.js index a24815b..61974c9 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -2948,7 +2948,13 @@ function renderRecordingInline(container) { function applyBearing() { let h = compassFrozen ? frozenHeading : baseHeading(); - let bearing = h - rotOffset; + // Do NOT subtract rotOffset. The whole .rec-screen (map + panel) is CSS-rotated + // by rotOffset in landscape; setBearing adds another CSS rotate on the map's + // rotatePane. Subtracting rotOffset cancelled the screen rotation FOR THE MAP, + // so the map stayed portrait-oriented (course glued to the narrow top edge) + // while the panel rotated. Using h lets the map turn with the screen like the + // panel → course points to the physical top. Portrait (rotOffset=0): h-0=h. + let bearing = h; map.setBearing(bearing); updateNorthArrow(bearing); } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 55b83e7..ddfff3a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -139,7 +139,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "baldav" -version = "0.1.363" +version = "0.1.366" dependencies = [ "base64 0.22.1", "btleplug", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1ea280a..e311f81 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "baldav" -version = "0.1.363" +version = "0.1.366" edition = "2021" [lib] diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4f619dd..f8fc13e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicegui/tauri-v2/refs/heads/main/tooling/cli/schema.json", "productName": "BalDav", - "version": "0.1.363", + "version": "0.1.366", "identifier": "dev.baldav.app", "build": { "frontendDist": "../frontend"