Files
HealthTracking/README.md
T

148 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div align="center">
<img src="src/assets/fightrate-logo.png" width="110" alt="FightRate logo">
# FightRate (HealthTracking)
**Continuous heart-rate capture for combat-sport training from a BLE chest strap — a bare React Native
app (no Expo) with a native Android foreground service, local SQLite storage and metrics computed only from
real timestamped BPM.**
![React Native](https://img.shields.io/badge/React_Native-0.86.0-informational)
![Android](https://img.shields.io/badge/Android-minSdk_24-informational)
![Node](https://img.shields.io/badge/Node-%E2%89%A5_22.11-informational)
[Features](#features) · [How it works](#how-it-works) · [Run locally](#run-locally) · [Known limitations](#known-limitations)
</div>
> [!NOTE]
> **Project status:** work in progress (`package.json` is `0.0.1`, Android `versionName` `1.0`). The
> `applicationId` is `com.healthtracking.fightrate`. The latest commits (2026-06-16) ship the
> "Instrumento Forte" UI refinement (v2): tabular JetBrains Mono numerics, animated live indicators and
> a zoomable chart. The record in [`.impeccable.md`](.impeccable.md) lists `npm run typecheck` with 0
> errors and `npm test -- --runInBand` with 74/74 passing for that release.
> [!IMPORTANT]
> **Real data only, Android only.** Every number on screen is computed from BPM samples received from a
> real sensor; with no samples the app shows an empty state instead of inventing a peak, zone, load or
> HRV. The BLE capture module is written in Kotlin (`android/…/capture/`); the `ios/` project has no
> native capture module.
## Features
| Tab | What it does |
|---|---|
| **Capture** | Live BPM, average / peak / sample-count tiles, BLE, battery and background status, and the training chart. Start/stop marks a **training window** inside the continuous stream — monitoring keeps storing samples either way |
| **History** | Saved training windows and continuous-usage blocks (split by gaps), each with average / peak / min BPM; open one to inspect the timestamped data; custom time range |
| **Sensor** | BLE scan, device list, connect, connection phase, signal (RSSI), battery level, BLE services/characteristics diagnostics and a shortcut to Android battery-optimization settings |
| **Insights** | Calculation matrix: current/average BPM, range, variability, time in zones, load, heart-rate drift, post-session recovery, HRmax/HRR (marked as needing a profile) and HRV (only when the strap sends RR intervals) |
- **Zoomable chart** (`TrainingChart`): pinch zoom, pan and a tap-to-pin marker, with tick granularity that
follows the visible span (half-second up to one-minute steps).
- **Zones** by fixed BPM thresholds: `BASE <150`, `PRESSÃO 150–169`, `REDLINE 170+`.
- Samples outside 30–240 bpm, or reported without skin contact, are discarded from metrics; a sample is
considered fresh for 12 s.
- UI copy is PT-BR; code identifiers are English.
> [!WARNING]
> **Grant the permissions and exempt the app from battery optimization.** The manifest requests Bluetooth
> scan/connect, fine location, notifications and a foreground service of type `connectedDevice`. Capture
> runs in that service; the **Bateria** button on the Sensor tab opens Android's battery-optimization
> settings (`REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` is declared) so the background capture isn't
> throttled.
## How it works
```mermaid
flowchart LR
A["Chest strap (BLE)<br/>Heart Rate 0x180D · 2A37"] --> B["FightRateCaptureService<br/>Kotlin foreground service"]
B --> C["FightRateCaptureStore<br/>SQLite"]
B --> D["FightRateCaptureModule<br/>native module · events"]
D --> E["useHeartRateMonitor<br/>React hook"]
C -. "samples / sessions" .-> D
E --> F["trainingMetrics · continuousUsage<br/>chartViewport"]
F --> G["Capture · History · Sensor · Insights"]
```
<details>
<summary>Technical details</summary>
| Piece | Where | Notes |
|---|---|---|
| BLE client | `FightRateCaptureService.kt` | Scans, connects by known device id, enables notifications on Heart Rate Measurement `2A37` (CCCD `2902`), reads Battery Level `2A19` from service `180F`, reads RSSI |
| Reconnect | same file | `START_STICKY` service; automatic reconnect with backoff 2 s → 5 s → 10 s → 30 s unless stopped manually; restores the last device after a restart; after 12 s without a valid sample it reports the stale state and waits for new real data |
| Parser | `HeartRateMeasurementParser.kt`, `src/ble/heartRateParser.ts` | Flags: UInt8/UInt16 BPM, contact detection, RR intervals (converted from 1/1024 s to ms) |
| Storage | `FightRateCaptureStore.kt` | `fightrate_rn_capture.db` (version 2): `workout_sessions` (training windows with device id/name), `continuous_heart_rate_samples` (timestamp, BPM, contact, RR intervals, device) and the legacy `heart_rate_samples`, migrated on upgrade |
| Bridge | `FightRateCaptureModule.kt`, `src/native/FightRateCaptureNative.ts` | Start/stop monitoring and capture window, connect, snapshot, session and continuous samples, battery-optimization settings; service broadcasts become JS events |
| Domain (pure TS) | `src/training/` | Live metrics, variability, drift, recovery, zones, insight dashboard, continuous-usage blocks (gap of 5 min starts a new block), chart viewport math |
| UI | `src/screens/`, `src/components/`, `src/theme/` | Tab shell in `src/app/FightRateApp.tsx` (state-driven; no navigation library), Reanimated 4 + Worklets animations, design tokens |
Design references: [`FIGHTRATE_DESIGN_SYSTEM.md`](FIGHTRATE_DESIGN_SYSTEM.md) (direction **01 — Corner
Instrument**, the official one) and, for history only,
[`FIGHTRATE_COMBAT_LAB_DESIGN_SYSTEM.md`](FIGHTRATE_COMBAT_LAB_DESIGN_SYSTEM.md) (deprecated direction — do
not reuse). Specs and plans live in [`docs/superpowers/`](docs/superpowers).
</details>
## Known limitations
- **Android only:** capture exists only in the Kotlin native module; there is no equivalent on iOS.
- **No athlete profile yet:** zones use the fixed BPM thresholds above; `%HRmax` zones, Karvonen and full TRIMP
are shown as requiring a profile — the app doesn't invent HRmax or resting heart rate.
- **HRV needs RR intervals:** without them the dashboard says so instead of estimating.
- **No navigation library:** tabs are driven by component state.
- **Release signing:** the `release` build type is configured with the **debug keystore**
(`android/app/build.gradle`); generate your own keystore before distributing.
- **Lint:** at the last record in [`.impeccable.md`](.impeccable.md), `npm run lint` fails on a pre-existing
`jest/globals` configuration error (not blocking typecheck or tests).
## Run locally
Requirements: Node ≥ 22.11 (`engines` in `package.json`), the Android SDK and a JDK. The project doesn't use Expo.
```bash
git clone https://git.evandro.dev.br/evandro/HealthTracking.git
cd HealthTracking
npm install
npm run typecheck
npm test -- --runInBand
npm run lint
```
```bash
npm run start # Metro
npm run android # run on a device/emulator
```
Debug APK:
```bash
cd android
./gradlew assembleDebug
# output: android/app/build/outputs/apk/debug/app-debug.apk
```
If Gradle reports `No Java compiler found`, point `JAVA_HOME` to the JBR bundled with Android Studio:
```bash
export JAVA_HOME="<Android Studio install dir>/jbr"
export PATH="$JAVA_HOME/bin:$PATH"
```
If React Native autolinking keeps a stale package after renaming Android package paths, clean the generated
Gradle caches before rebuilding:
```bash
rm -rf android/.gradle android/app/build android/build .gradle
cd android && ./gradlew assembleDebug
```
## Notices
- The literature notes in [`docs/data/pesquisa.md`](docs/data/pesquisa.md) state that population tables and
formula-based estimates are not a substitute for measured testing.
- Built on [React Native](https://reactnative.dev/), [Reanimated](https://docs.swmansion.com/react-native-reanimated/),
[Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/),
[react-native-svg](https://github.com/software-mansion/react-native-svg),
[Lucide](https://lucide.dev/) icons and [JetBrains Mono](https://www.jetbrains.com/lp/mono/).