Remove the home-city name from docs/comments/UI strings and the external mirror IP (WAN) from code, docs and the Android cleartext allowlist, replacing the IP with mirror.invalid (set the real host locally, uncommitted). LAN mirror and routing keep working; the WAN fallback needs the real host set. Generic big-city map presets are kept (not identifying).
20 KiB
20 KiB
BalDav
Персональный трекер здоровья и физической активности.
Возможности
- Дневник питания — ~13000 продуктов с меткой источника (Магнит, 5ка, ИОН, KDV, Гриднев, свои). Поиск порядко-независимый (слова в любом порядке). Каталог чистится от неиспользованного импорта без метки (health-diet и пр.) кнопкой в настройках — съеденные и в рецептах сохраняются. 37 нутриентов (витамины, минералы, omega-3/6, вода, клетчатка). Рецепты с термообработкой. Формулы порций (4*30, 300+50).
- GPS трекинг — запись маршрутов на велосипеде/пешком в фоне (foreground service, точки пишутся при выключенном экране; один файл на сессию с id сессии в имени
gps_points_<ms>_s<id>.jsonlдля однозначной привязки, переживает START_STICKY-перезапуск ОС; полный до-импорт нативных точек по timestamp при финализации — без потерь, даже если экран был выключен всю запись, а при пробуждении экрана точки видимо нарастают на карте по мере импорта). При живой записи нативный файл — источник истины для БД, watchPosition только рисует маркер (без дублей одного фикса с разными ts). Восстановление точек из файлов на устройстве — матч файла к сессии по id/дате, дозаполнение разрывов с дедупом по близости ±2.5с, безопасный пересчёт без удаления точек, чистка уже импортированных файлов. Оффлайн карты (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м, вело/пешие). Настраиваемый цвет линии трека (чтобы отличать от цветных дорог). Навигационный маркер позиции с треугольником-направленцем (по компасу или GPS-курсу); порядок слоёв тайлы<дороги<маршрут<трек<маркер. Импорт тайлов по регионам (SQLite, роутер LAN). - BLE пульсометр — подключение к FitPro браслетам и стандартным HR датчикам (0x180D). ЧСС, SpO2, давление в реальном времени при записи трека.
- Расчёт ккал — велосипед: НЕТТО-модель (суточный баланс уже учитывает BMR, поэтому активность считается сверх покоя). Кручение = max(физика мощности из gradient+Crr+CdA / КПД, эмпирический MET-нетто по скорости); накат (freewheel,
power≤0— «велик катился бы сам») = только базовый обмен. Crr на грунтах уточняется по вибрации акселерометра (до 0.020 на траве/колее). Ходьба с велосипедом в горку (только односкоростной) отделяется автоматически по скорости ниже каденса 30 об/мин и не засчитывается как езда. MET-таблица для ходьбы/бега и тренировок (приседания 6.0, скакалка 12.3, растяжки 2.3, бассейн 8.0). Набор высоты — кумулятивный гистерезис (устойчив на плотных треках, где per-point разница <1м); абсолютная точность набора ограничена сырым GPS-альтиметром (порядка десятков процентов, без модели рельефа не разрешается). Известное ограничение:max(физика, MET)не складывает накладные расходы, поэтому подъёмы по ккал слегка недосчитываются. Пункт меню «Пересчитать высоту/калории» пересчитывает статистику всех треков без изменения точек. Профили велосипедов. BMR по Mifflin-St Jeor с коэффициентом активности. - Дистанция на разрывах GPS — на «слепых» участках (интервал между фиксами >
gapThresholdSec) путь считается по скорости (speed×dt, с ограничением по макс. скорости), а не прямой хордой — иначе разрывы занижают/искажают длину. Телепорт-скачки без временного разрыва отбрасываются. Расчёт зависит только от времени и скорости, поэтому live-счётчик, «Стоп» и пересчёт дают одинаковую длину. ПорогgapThresholdSecнастраивается; при изменении и пересчёте применяется сразу. - Тренировки — приседания (подходы x повторения), скакалка, растяжки, бассейн (дистанция). Автоматический расчёт ккал по MET и весу.
- Нутриенты — 37 микронутриентов (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10). Нормы РСП/АУП (РФ/ЕС/США). Фармакокинетика — запасы в организме по полураспаду.
- Трекинг жидкости — water_pct в продуктах, прогресс-бар, настраиваемая норма.
- Графики — скорость, высота, ЧСС тренировки с зумом и pan. Тренд веса. Донат макронутриентов. Баланс ккал (приход/расход).
- OTA обновления — проверка BUILD_NUMBER на роутере (LAN) или VPS, скачивание через браузер.
- Ручные маршруты — рисование трека на карте тапами, elevation из прошлых треков или open-meteo API.
- Сохранённые маршруты — импорт GPX-маршрутов, нарисованных заранее (например, в конструкторе Яндекс.Карт: формат
<rtept>), выбор активного, наложение цветной линией поверх карты во время записи И на экране просмотра прошлого трека — для визуальной навигации. Управление на отдельной странице (импорт/переименование/цвет/удаление, один активный за раз), переключение линии кнопкой-тогглом. Цвета «проезженных дорог» настраиваются раздельно для вело/пеших/бега. - Высоты маршрута (рельеф до поездки) — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом уклон/высота: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (
<ele>из GPX или онлайн) тянутся по цепочке: GPXZ (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM). - Редактор маршрутов — создание («Новый маршрут») и правка сохранённого маршрута прямо в приложении: тап по карте — добавить точку в конец, тап по линии — вставить точку в это место, тащить точку — двигать, тап по точке — удалить. Высоты подтягиваются онлайн после отрисовки (из предпросмотра). Тумблер «🚲 По дорогам» прокладывает линию между опорными точками по реальным дорогам (BRouter, профиль trekking) и сохраняет дорожную геометрию; выключен — прямые отрезки.
- Оффлайн-роутинг (BRouter) — движок BRouter встроен в приложение (Android): маршруты строятся по дорогам без сети, сегменты
.rd5региона качаются с домашнего зеркала прямо в приложении (экран дорог → «Сегменты»). Используется в редакторе маршрутов («По дорогам») и для заполнения GPS-пропусков. На десктопе/дома движка нет — те же вызовы уходят на домашний BRouter-RouteServer по HTTP. - Заполнение GPS-пропусков по дороге — на детали трека тумблер «🧩» прокладывает дорожный путь (BRouter, профиль shortest) через каждый GPS-дропаут (разрыв по времени + реальная хорда) и рисует его голубым пунктиром поверх трека; если дорога в разы длиннее хорды — оставляет хорду. Оверлей, данные трека не меняет.
- Де-джиттер трека — сглаживание дрожащего GPS-трека методом Савицкого-Голея (настраиваемое окно/число итераций, живое превью на карте детали трека). Хранится обратимо: оригинал и сглаженный вариант лежат раздельно, калории пересчитываются по сглаженным точкам, откат к оригиналу в один тап. Точность каждого фикса (
accuracy_m) пишется в БД: при пересчёте трека точки хуже порога «Макс. погрешность GPS» исключаются из расчёта и линии (обратимо — точки в БД остаются; поднять порог = вернуть). Работает во всех пересчётах (кнопка «Пересчитать», «Пересчитать всё», после де-джиттера); импорт/старьё без точности и dead-reckoning заполнения не трогает. - POI на карте — точки интереса (кафе, магазины, вода, туалеты, банки/ATM, остановки, укрытия, смотровые и т.д.) круглыми контрастными значками (эмодзи в кружке, читаемо на светлой и тёмной карте). Кнопка
📍с панелью-фильтром категорий; рендер по видимой области от зума 13. Данные оффлайн (data/pois.json, ~100 км вокруг опорной точки, 5662 объекта из OSM/Overpass). На картах записи, деталей трека и дорог. - Тёмная карта — тумблер
☾на экранах записи, деталей трека и дорог: инвертирует ТОЛЬКО слой тайлов (invert + hue-rotate 180° + brightness, трек/маршрут/дороги остаются цветными), выбор запоминается. Работает одинаково на офлайн-тайлах и онлайне, без ключей/квот/перепаковки. - Метки ветра — кнопка «Ветер» на экране записи: тап открывает интервал встречного ветра, тап закрывает. Метки хранятся с треком (
wind_marks) как эталон для проверки оценки ветра по треку; на расчёт калорий/мощности не влияют. Голосовые метки — на будущее. - Экспорт/импорт — свои продукты, рецепты и добавки одним файлом (JSON).
- Вес, сон, добавки — логирование с историей.
Стек
- Frontend: чистый JavaScript, HTML, CSS. Без React/Vue/bundler.
- Backend: Rust (Tauri 2) — SQLite, IPC, seed import, BLE (btleplug на десктопе).
- Android: Kotlin — foreground GPS service, BLE HR service, WakeLock, SAF.
- Библиотеки: Leaflet 1.9.4 (карты), leaflet-rotate (компас), Chart.js (графики), Hammer.js (жесты), JSZip.
Сборка
# Зависимости: Rust, Node.js, Android SDK + NDK
./build.sh -d android # debug APK
./build.sh android # release APK
./build.sh -i android -d # debug + установка на устройство
./build.sh -u android # release + upload на роутер (LAN) и VPS (OTA)
./build.sh -d -i -u android # debug + install + upload
Версия и трассируемость сборки
Чтобы по розданному APK однозначно находить, где регресс/косяк:
versionName 0.1.NNN— монотонныйBUILD_NUMBER(инкремент на каждой сборке вbuild.sh), первичный уникальный идентификатор билда. Виден в Настройках и в свойствах пакета Android.- git-хэш коммита исходников зашивается в бинарник при компиляции (
src-tauri/build.rs→GIT_HASH) и показывается в Настройках:v0.1.NNN (<hash>) <дата>. Хэш всегда свежий (build.rsперезапускается при смене коммита черезcargo:rerun-if-changed), с суффиксом-dirty, если отслеживаемый код (frontend/,src-tauri/src/) не закоммичен. - Хэш = HEAD на момент компиляции.
build.shбампает версию, но НЕ коммитит, поэтому в dev-сборке хэш = последний коммит, а код может быть впереди (тогда-dirty). Для раздачи: закоммить код перед release-сборкой — тогда хэш чистый и APK точно соответствует коммиту.
Решение о точном встроенном хэше принято на коммите b4fdc824144 (ветка master, 2026-08-16).
Структура
Подробно: PROJECT_MAP.md
frontend/
js/main.js — UI, навигация, модалки, компас, ландшафт (~5500 строк)
js/db.js — БД, миграции, CRUD (~1500)
js/gps.js — GPS, физика ккал, BLE HR polling (~1200)
js/bmr.js — BMR формула, РСП, полураспад нутриентов
js/sync.js — синхронизация между устройствами (LAN)
js/tilecache.js — оффлайн тайлы (IndexedDB)
css/styles.css — стили, тёмная тема, ландшафтный режим
img/gps-placeholder.png — пиксельарт заглушка до GPS
data/products_seed.db — 13480 продуктов
src-tauri/
src/lib.rs — Rust бэкенд, seed import
src/ble.rs — BLE HR (btleplug десктоп, file IPC Android)
gen/android/ — Kotlin: MainActivity, GpsRecorderService, BleHrService
tools/
5ka_scraper.user.js — Tampermonkey скрейпер 5ка
magnit_scraper.user.js — Tampermonkey скрейпер Магнит
ion_scraper.user.js — Tampermonkey скрейпер ИОН
smart/
protocol.js — FitPro BLE протокол (reverse-engineered)
hr-reader.js — Node.js прототип чтения ЧСС
Данные продуктов
Каталог (seed) очищен от импорта без метки источника (health-diet, ашан, фастфуд) — остаются источники с плашкой:
| Источник | Продуктов | Витамины/минералы |
|---|---|---|
| Магнит (SSR парсинг) | 7690 | нет |
| 5ка (API скрейпер) | 3354 | нет |
| ИОН (web.ion.ru) | 1079 | да |
| KDV (SSR парсинг) | 667 | нет |
| Гриднев (PDF) | 217 | да |
| Свои (manual) | — | по вводу |
| Всего (seed) | ~13000 |
На устройстве такая же чистка — кнопка «Очистить продукты без магазина» в настройках (удаляет только неиспользованные без метки; съеденные/в рецептах остаются).