42 KiB
BalDav
Персональный трекер здоровья и физической активности.
Текущие версии (2026-09-22, build 474)
| Что | Версия |
|---|---|
| Приложение (BalDav) | 0.1.474, build 474 |
| Выкачено на роутер (OTA) | build 474 - http://192.168.1.1:88/baldav/baldav.apk |
| Резерв OTA (VPS) | build 474 - https://4aiman.top/baldav/baldav.apk |
| Встроенный движок BRouter | 1.6.3 |
Сид продуктов (products_seed.db) |
11 |
| Сборка | Tauri v2, Android arm64, debug-подпись |
Версия/номер сборки бампаются автоматически при ./build.sh android (файлы BUILD_NUMBER,
tauri.conf.json, Cargo.toml/lock). Телефон сам проверяет обновление: сначала роутер
(LAN 192.168.1.1:88/baldav), затем VPS - берёт BUILD_NUMBER, если больше локального,
предлагает скачать baldav.apk. Выкатка на роутер - существующим APK (без пересборки, чтобы
номер не уезжал) либо ./build.sh android -d -u (соберёт новый номер и зальёт на роутер+VPS).
Возможности
- Дневник питания — ~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). Онлайн-долечивание пустых тайлов: раз в 30с, когда OSM доступен, БалДав дофетчивает недостающие тайлы (текущий зум) по видимой области и вдоль проеханного маршрута, кэширует и перерисовывает - дырки, оставшиеся от плохого сигнала в поездке, закрываются сами, когда связь есть (бережно к OSM: троттлинг, лимит на цикл; в режиме «офлайн карты» не фетчит). - BLE пульсометр — подключение к FitPro браслетам и стандартным HR датчикам (0x180D). ЧСС, SpO2, давление в реальном времени при записи трека. Подключение только вручную (Настройки -> «Подключить»), в т.ч. во время записи - пульс пишется в трек с момента подключения; автопоиска при старте записи нет (экономия батареи). BT выключен -> кнопка просит включить (системный диалог). Браслет не найден в скане -> сервис не висит в коннекте.
- Зарядка (пульс) — режим записи БЕЗ GPS для домашней тренировки: геолокация не включается и не пишется, только пульс с датчика + таймер. Экран без карты (крупный пульс, время, живые ккал, средний/макс). Нагрузка (ккал) считается по пульсу формулой Keytel (нетто, как и остальная активность) и идёт в дневной баланс. Деталь - без карты, график пульса по HR-зонам. Хранится в
gps_sessions(activity_type="workout", без точек, ряд пульса вhr_samples). Режим «Руки/VR (пульс)» (activity_type="vr") - то же самое для махательных/танцевальных/VR-тренировок, трекается отдельно от зарядки. - Расчёт ккал — велосипед: НЕТТО-модель (суточный баланс уже учитывает 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_sessions.rider_weight_kg), поэтому пересчёт старого трека считает по тогдашнему весу, а не текущему (старые треки без сохранённого веса - по последнему взвешиванию). Поле веса в настройках авто-синкается с последним взвешиванием. Баланс ккал вверху, банк за 7 дн. (накопительная сумма дневных балансов + перевод в кг - «сколько доесть/недоесть»), тренд «Среднее за 7 дн.» и графики (вес/баланс, окна 30/90) считаются от выбранного дня (окно кончается на нём), а не всегда от «сегодня». График «вес/баланс»: ось баланса флотирует симметрично вокруг 0 (пол ±500, без фикс-потолка), чтобы жёсткий день не сплющивал линию. Опциональный TEF (термический эффект пищи, галочка в профиле, 10% прихода): вводится с сохранением поддержки - база = BMRactLevel(1-0.1), затем +0.1*приход, так что на поддержке баланс не меняется, а в дефиците расход чуть ниже (ел меньше - переварил меньше). В сводке дня строка «расход: база + переваривание + активность». - Дистанция на разрывах GPS — на «слепых» участках (интервал между фиксами >
gapThresholdSec) путь считается по скорости (speed×dt, с ограничением по макс. скорости), а не прямой хордой — иначе разрывы занижают/искажают длину. Телепорт-скачки без временного разрыва отбрасываются. Расчёт зависит только от времени и скорости, поэтому live-счётчик, «Стоп» и пересчёт дают одинаковую длину. ПорогgapThresholdSecнастраивается; при изменении и пересчёте применяется сразу. - Тренировки — приседания (подходы x повторения), скакалка, растяжки, бассейн (дистанция). Автоматический расчёт ккал по MET и весу.
- Нутриенты — 37 микронутриентов (витамины, минералы, жирные кислоты, женьшень, ресвератрол, CoQ10). Нормы РСП/АУП (РФ/ЕС/США). Фармакокинетика — запасы в организме по полураспаду.
- Трекинг жидкости — water_pct в продуктах, прогресс-бар, настраиваемая норма. На тренировке (велозаезд/VR) - уровневое напоминание пить: на старте приложение берёт твой водный баланс из лога (норма ко времени дня минус выпитое, посчитанное из залогированной жидкости), дальше нативный сервис копит потребность (пот) за активность; громкий сигнал на канале будильника (пробивает громкость) только когда отстаёшь сверх порога. Вода - из того, что логируешь, отдельной кнопки нет.
- Графики — скорость, высота, ЧСС тренировки с зумом и pan. Тренд веса. Донат макронутриентов. Баланс ккал (приход/расход).
- OTA обновления — проверка BUILD_NUMBER на роутере (LAN) или VPS, скачивание через браузер.
- Ручные маршруты — рисование трека на карте тапами, elevation из прошлых треков или open-meteo API.
- Сохранённые маршруты — импорт GPX-маршрутов, нарисованных заранее (например, в конструкторе Яндекс.Карт: формат
<rtept>), выбор активного, наложение цветной линией поверх карты во время записи И на экране просмотра прошлого трека — для визуальной навигации. Управление на отдельной странице (импорт/переименование/цвет/удаление, один активный за раз), переключение линии кнопкой-тогглом. Цвета «проезженных дорог» настраиваются раздельно для вело/пеших/бега. - Высоты маршрута (рельеф до поездки) — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом уклон/высота: уклон по НАПРАВЛЕНИЮ движения, полосами по 1%: подъём зелёный→жёлтый→красный→коричневый→чёрный, спуск зелёный→cyan→синий→navy (≥10% крайний), или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (
<ele>из GPX или онлайн) тянутся по цепочке: GPXZ (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; уклон считается по дистанционному окну ~30м (одна ячейка DEM), чтобы ступенька SRTM не давала ложный крутой уклон на одном коротком сегменте. Поправка высот по проездам: где катал >=N раз, SRTM-высота в превью сдвигается вверх/вниз на медиану (измеренная−SRTM) по ячейкам ~20м (SRTM остаётся базой, недеструктивно;get_elev_correction). - Редактор маршрутов — создание («Новый маршрут») и правка сохранённого маршрута прямо в приложении: тап по карте — добавить точку в конец, тап по линии — вставить точку (широкая зона попадания), долгий тап по линии — переключить отрезок дорога/прямая, тащить точку — двигать, тап по точке — удалить. Роутинг по-сегментный: каждый отрезок между опорными точками отдельно либо по дорогам (BRouter, профиль trekking, высоты из BRouter), либо прямой; по дорогам по умолчанию, где дороги/сегментов нет — молча прямая; кнопка «🚲 По дорогам» задаёт режим сразу всем. Маршрут в редакторе рисуется оковкой с раскраской уклон/высота как в детали; тумблеры день/ночь, уклон/высота и кнопка «моё место» на карте. Сохраняются и опорные точки (5-6, для правки — при повторном открытии столько же), и плотная дорожная геометрия с высотами (для показа и профиля уклона).
- Оффлайн-роутинг (BRouter) — движок BRouter встроен в приложение (Android): маршруты строятся по дорогам без сети, сегменты
.rd5региона качаются с домашнего зеркала прямо в приложении (экран дорог → «Сегменты»). Используется в редакторе маршрутов («По дорогам») и для заполнения GPS-пропусков. На десктопе/дома движка нет — те же вызовы уходят на домашний BRouter-RouteServer по HTTP. Профильtrekkingпо умолчанию избегает бродов (ford), заброшенных грунтовок (tracktype=grade5) и троп с плохой видимостью (trail_visibility=bad|horrible|no) - штраф в costfactor. В редакторе тумблер «Броды: избегать/разрешить» переключает наtrekking-adventure(allow_adventure). Показ опасности: дорожные ноги строятся через geojson (format=geojsonдаётWayTags/NodeTagsна сегмент; on-deviceformatAsGeoJson), броды и грунт/тропы помечаются на карте + счётчик «⚠ брод/грунт» - видно ДО поездки (GPS.parseRouteGeoJson). - Заполнение GPS-пропусков по дороге — на детали трека кнопка «🧩» прокладывает дорожный путь (BRouter, профиль shortest) через каждый GPS-дропаут (разрыв по времени + реальная хорда) и показывает предпросмотр только достроенных пропусков (голубым, с бело-чёрной оковкой; недостроенные — красным пунктиром) плюс панель «Достроено N (+X км), M не удалось» с кнопками Сохранить/Отмена. Сохранение — обратимый патч на месте: достроенные точки помечаются
estimatedи добавляются в трек (оригинал цел), статистика пересчитывается; повторный тап предлагает «Убрать достройку» (удаляет только достроенное). Достраиваются только реальные длинные потери сигнала (по умолчанию разрыв > 60с и хорда > 150м); пропуск не достраивается, если дорога заметно длиннее хорды (крюк, > 2×) или почти совпадает с прямой (мостить нечего) — чтобы не перерисовывать нормальные участки объездами. - Непроезжие места (nogo) — красные круги, которые роутинг ОБЪЕЗЖАЕТ. Ставятся кнопкой ⛔: в планировщике — тапом по карте/линии, на карте записи — «здесь нет проезда» (текущее место). Показываются на картах планировщика, записи и детали трека; тап по кругу — убрать. Избегание работает и через домашний RouteServer, и встроенным движком офлайн (BRouter nogopoints). Web-BRouter умеет nogo сам.
- Объезд с возвратом на маршрут (на ходу) — на карте записи кнопка «↪»: если активный маршрут ведёт через непроезжее место, строит от текущей позиции объезд обратно НА маршрут за местом затыка (отступ ~1.5 км вперёд, растёт при неудаче до конца маршрута), избегая nogo и разворота на 180° (учитывает курс). Показывается ярким навигационным оверлеем (не меняет сохранённый маршрут и не прерывает запись); долгий тап по кнопке убирает оверлей.
- Детект ходьбы в вело-треке - устойчиво низкая скорость (по умолчанию < 8 км/ч дольше 60с; порог уточняется по передаче для односкоростного) в вело/другой сессии = ведёшь/идёшь пешком. Такие участки красятся отдельно (цвет ходьбы) на детали трека, в статах показывается «пешком: N мин / X км», и они НЕ попадают в «проезженные дороги» (не строятся по ним). Флаг хранится по точкам (
gps_points.walk), пересчитывается на «Пересчитать»/«Пересчитать всё». - Роутинг сначала по проезженному - публичные данные BRouter часто не совпадают с землёй (роутит по несуществующим дорогам). Поэтому планировщик для каждой дорожной ноги сначала пробует проложить путь по дорогам, где ты реально ездил (граф из своих треков: узлы на сетке ~20м, исключая пешие куски), и только если проезженного рядом нет или путь выходит сильно кривой - обращается к BRouter. Ридден-нога рисуется тонкой линией (без высот), BRouter-нога - оковкой с раскраской. Граф строится из
gps_pointsкомандойget_ride_graph, кэшируется. - Объединение поездок - долгий тап по треку в истории включает выделение, тап отмечает ещё; когда выбрано 2+, кнопка «Объединить» склеивает их в одну тренировку (точки всех сессий переносятся в самую раннюю по времени, порядок по таймстампу, стата пересчитывается). Полезно, когда одна тренировка записалась несколькими кусками (например, отдельный заезд оказался в своей сессии - после склейки он попадёт в «Разбор повторов»). Разрыв между треками больше 5 минут - предупреждает, но не мешает. Разные велосипеды склеить нельзя - показывает предупреждение (передача/вес отличаются, мощность и каденс посчитались бы неверно); если это один велосипед, задай его трекам одинаково и склеивай. Обратимо: у каждой точки хранится исходная сессия (
gps_points.orig_session_id). - Разрезание трека - в детали трека кнопка «Разрезать трек» включает режим: тап по точке на карте -> в её окошке кнопка «✂ Разрезать здесь» -> подтверждение -> второй кусок становится отдельной тренировкой (с тем же велосипедом; порядок точек по времени). Зачем: если часть трека проехана на другом велосипеде или другой передаче - разрежь в точке смены, назначь каждому куску свой велосипед (в детали: «Велосипед» + «Пересчитать»), и мощность/каденс посчитаются по правильной передаче на каждом куске.
- Резервная копия всех данных - в Настройках Экспорт сливает всю базу (
baldav.db) в файл через SAF (export_db->VACUUM INTOчистой сжатой копии, ~на треть меньше), Импорт выбирает файл, проверяет его (PRAGMA quick_check+ наличие таблиц BalDav) и восстанавливает с ЗАМЕНОЙ: файл кладётся вpending_restore.db, приложение перезапускается, и Kotlin вonCreate(до открытия БД) свопит его наbaldav.db. Секретов в базе нет. Зачем: переносить наработки между сборками (debug <-> release - разные подписи, переустановка стирает данные) без сторонних бэкаперов. Внутриприложенческой авто-установки APK намеренно нет - только экспорт/импорт данных. - Разбор повторов (интервалы) - катаешь один подъём несколько раз за поездку (один трек, без стоп/старт на каждом заезде) - деталь трека сама находит повторяемый участок по самоподобию (сетка ~10м, повторно посещённые ячейки, самый длинный эталонный проход) и в сворачиваемой секции «Разбор повторов» показывает таблицу заездов (номер, время, скорость, каденс, мощность) и главное число - последний / первый, с вердиктом: >=100% зелёный (можно завтра), 90-99% жёлтый (тяжёлая норма), <90% красный (два дня спокойно). Каждая точка проецируется на опорную линию (снос вбок в пределах ~30м, например при возне с седлом, не режет покрытие); время заезда - только секунды в движении (остановки вырезаются, скорость нормируется на длину участка). Найденный участок подсвечивается на карте. Только вело.
GPS.detectRepeats; повторов нет (обычная поездка A-B) - так и пишет. - Похвастаться (шэр-картинка) - в детали трека кнопка «Похвастаться»: генерирует картинку в формате сторис (9:16) с формой трека и статами (Дистанция/Время/Средняя скорость крупно + набор/ккал/дата), прозрачную (PNG с альфой для наложения на своё фото) или на фото-подложке из настроек. В модалке предпросмотра галочками выбирается, что класть на картинку (форма трека, дистанция, время, средняя/макс. скорость, набор высоты, калории, дата; выбор запоминается), переключатель прозрачный/фото и кнопки Поделиться (системный шэр) и Сохранить (SAF). Подложка выбирается в Настройках.
- Сегменты - сворачиваемо - в детали трека список сегментов высот (climb/descent, до десятков штук) свёрнут в аккордеон (
<details>, свёрнут по умолчанию, счётчик в заголовке); тап разворачивает/сворачивает. Кнопки внизу (Похвастаться, Экспорт, Пересчитать, Удалить) остаются доступны без долгого скролла. - Де-джиттер трека — сглаживание дрожащего GPS-трека методом Савицкого-Голея (настраиваемое окно/число итераций, живое превью на карте детали трека). Хранится обратимо: оригинал и сглаженный вариант лежат раздельно, калории пересчитываются по сглаженным точкам, откат к оригиналу в один тап. Точность каждого фикса (
accuracy_m) пишется в БД: при пересчёте трека точки хуже порога «Макс. погрешность GPS» исключаются из расчёта и линии (обратимо — точки в БД остаются; поднять порог = вернуть). Работает во всех пересчётах (кнопка «Пересчитать», «Пересчитать всё», после де-джиттера); импорт/старьё без точности и dead-reckoning заполнения не трогает. - POI на карте — точки интереса (кафе, магазины, вода, туалеты, банки/ATM, остановки, укрытия, смотровые и т.д.) круглыми контрастными значками (эмодзи в кружке, читаемо на светлой и тёмной карте). Кнопка
📍с панелью-фильтром категорий; рендер по видимой области от зума 13. Данные оффлайн (data/pois.json, ~100 км вокруг опорной точки, 5662 объекта из OSM/Overpass). На картах записи, деталей трека и дорог. - Тёмная карта — тумблер
☾на экранах записи, деталей трека и дорог: инвертирует слой тайлов (invert + hue-rotate 180° + brightness, трек/маршрут остаются цветными), выбор запоминается. Оверлей дорог получает ТОТ ЖЕ фильтр (панroadsPane), чтобы его цвета сдвигались вместе с тайлами и оставались контрастными на тёмной карте. Работает одинаково на офлайн-тайлах и онлайне, без ключей/квот/перепаковки. - Метки ветра — кнопка «Ветер» на экране записи: тап открывает интервал встречного ветра, тап закрывает. Метки хранятся с треком (
wind_marks) как эталон для проверки оценки ветра по треку; на расчёт калорий/мощности не влияют. Голосовые метки — на будущее. - Слежение карты (тумблер
⊕) — на экране записи карта по умолчанию центрируется на текущей позиции при каждой новой точке. Кнопка⊕(подсвечена = слежу) выключает автоцентрирование: карту можно свободно подвигать и осмотреться, когда «где я» непонятно (отклонился от трека или трек ведёт туда, где дороги нет), а новые точки её не возвращают. Повторный тап центрирует на текущей позиции и включает слежение снова. По умолчанию включено при каждом заходе (не запоминается). - Трансляция местоположения (опционально) — галка в профиле: пока включено, во время записи заезда телефон раз в 10с отправляет текущую точку на свой сервер (botovod на VPS, endpoint
/api/live/push, авторизация токеном - адрес и токен хранятся только на устройстве). Карта/liveпоказывает точку и короткий хвост и доступна только после входа тем, кто в списке доступа (SRP-авторизация botovod). Выключено по умолчанию, диагностика пушей вlive_share_log.jsonl. - СМС «я тут» — настраивается контакт (ввод номера вручную ИЛИ выбор из системных контактов); во время записи кнопка «СМС» по подтверждению шлёт на него текст с координатами и ссылкой на Яндекс.Карты. Если дано разрешение SEND_SMS - отправляется напрямую по сотовой сети (работает на 2G/EDGE без интернета), иначе открывается системный SMS-композер с предзаполненным текстом.
- Экспорт/импорт — свои продукты, рецепты и добавки одним файлом (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
./build.sh linux # ПК Linux: .deb, .rpm, .AppImage
./build.sh windows # ПК Windows: NSIS-установщик (кросс, mingw)
./build.sh macos # ПК macOS: .app в zip, Intel + Apple Silicon (кросс, osxcross)
./build.sh pc # linux + windows + macos
./build.sh all # android + pc
./build.sh -da upload # = -u all, APK debug (как на телефоне), ПК release -> роутер
./build.sh -u android windows # любая комбинация целей и флагов
Цели и флаги комбинируются как угодно; за запуск номер сборки растёт один раз и общий для всех
целей. -d - debug для всего, -da - debug только для APK. -u выкладывает в
http://192.168.1.1:88/baldav/ всё, что собралось в этом запуске: baldav.apk,
baldav-linux-amd64.deb, baldav-linux-x86_64.rpm, baldav-linux-x86_64.AppImage,
baldav-windows-x64-setup.exe, baldav-macos-x64.zip, baldav-macos-arm64.zip, рядом с каждым
<файл>.build с номером сборки. BUILD_NUMBER (по нему телефон проверяет OTA) пишется только
вместе с APK, иначе телефону предложилось бы «обновление» на старый APK; APK, как и раньше, уходит
ещё и на VPS. Упавшие цели не мешают выкладке остальных и перечисляются в итоге. Кросс-сборка под
macOS берёт osxcross из $OSXCROSS (по умолчанию /mnt/02F6E7F6F6E7E7BD/osxcross, SDK 12.3 из
fpcupdeluxe); .app собирается скриптом, т.к. бандлер Tauri на Linux его пропускает.
Версия и трассируемость сборки
Чтобы по розданному 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 |
На устройстве такая же чистка — кнопка «Очистить продукты без магазина» в настройках (удаляет только неиспользованные без метки; съеденные/в рецептах остаются).