diff --git a/PROJECT_MAP.md b/PROJECT_MAP.md index 7459076..828f44f 100644 --- a/PROJECT_MAP.md +++ b/PROJECT_MAP.md @@ -49,7 +49,7 @@ | Файл | Строк | Назначение | |------|-------|------------| | `MainActivity.kt` | ~300 | FileObserver IPC, seed DB, safe insets, SAF, BLE commands, OTA check | -| `GpsRecorderService.kt` | ~500 | Foreground service GPS, WakeLock, JSONL, 50Hz акселерометр/гироскоп, dead reckoning, raw sensor files | +| `GpsRecorderService.kt` | ~500 | Foreground service GPS, WakeLock, JSONL (один файл на сессию до Стоп, идемпотентный onStartCommand при START_STICKY-перезапуске ОС), 50Hz акселерометр/гироскоп, dead reckoning, raw sensor files | | `BleHrService.kt` | ~340 | Foreground service BLE HR, FitPro protocol, GATT queue, scan/connect | | `generated/*.kt` | ~1100 | Tauri-сгенерированные: WebView, IPC, permissions | diff --git a/README.md b/README.md index 27faaf2..233eefa 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 трекинг** — запись маршрутов на велосипеде/пешком в фоне. Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация). Ландшафтный режим (CSS rotation + панель справа, 3 режима: off/auto/lock). Dead reckoning при потере GPS (гироскоп + акселерометр). Placeholder до GPS (пиксельарт). Карта проезженных дорог (дедупликация 2м, вело/пешие). Импорт тайлов по регионам (SQLite, роутер LAN). +- **GPS трекинг** — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию, переживает START_STICKY-перезапуск ОС). Оффлайн карты (Leaflet + IndexedDB кэш). Импорт GPX/FTB (FitoTrack). Экспорт GPX. Фильтр altitude outlier'ов и GPS-залипаний. Вращение карты по компасу (leaflet-rotate, EMA-сглаживание, gimbal lock компенсация). Ландшафтный режим (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/src-tauri/gen/android/app/src/main/java/dev/baldav/app/GpsRecorderService.kt b/src-tauri/gen/android/app/src/main/java/dev/baldav/app/GpsRecorderService.kt index 2f6be10..56474f1 100644 --- a/src-tauri/gen/android/app/src/main/java/dev/baldav/app/GpsRecorderService.kt +++ b/src-tauri/gen/android/app/src/main/java/dev/baldav/app/GpsRecorderService.kt @@ -95,15 +95,24 @@ class GpsRecorderService : Service(), LocationListener, SensorEventListener { // Points file in app root data dir (same as Rust app_data_dir) val appDataDir = filesDir.parentFile ?: filesDir - // Each session gets its own file — never overwrite previous sessions. - // Old files are only deleted by JS after confirmed DB insert. - val sessionTs = System.currentTimeMillis() - pointsFile = File(appDataDir, "gps_points_$sessionTs.jsonl") - // Write current filename so JS knows which file is active - File(appDataDir, "gps_live_points.jsonl").writeText(pointsFile!!.name) + // Keep writing to ONE file until "Stop". A genuine start from JS carries + // an intent (with config extras); a START_STICKY restart by the OS comes + // with a null intent — in that case resume the existing session file so + // background points are never orphaned. + val ptrFile = File(appDataDir, "gps_live_points.jsonl") + val existingName = if (ptrFile.exists()) ptrFile.readText().trim() else "" + if (intent == null && existingName.endsWith(".jsonl") && File(appDataDir, existingName).exists()) { + // START_STICKY restart by OS — keep appending to the same session file + pointsFile = File(appDataDir, existingName) + Log.d(TAG, "Service restarted by OS — resuming session file $existingName") + } else { + // Genuine start from JS — new session file + pointsFile = File(appDataDir, "gps_points_${System.currentTimeMillis()}.jsonl") + ptrFile.writeText(pointsFile!!.name) + } // Raw sensor file (50Hz, ~5MB/hour) with rotation (keep last 15) - rawSensorFile = File(appDataDir, "sensor_raw_$sessionTs.jsonl") + rawSensorFile = File(appDataDir, "sensor_raw_${System.currentTimeMillis()}.jsonl") // Rotate: delete oldest raw files if more than 15 try { val rawFiles = appDataDir.listFiles { f -> f.name.startsWith("sensor_raw_") && f.name.endsWith(".jsonl") }