2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00
2026-06-09 20:30:01 -03:00

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 Android Node

Features · How it works · Run locally · Known limitations

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 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

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"]
Technical details
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 (direction 01 — Corner Instrument, the official one) and, for history only, FIGHTRATE_COMBAT_LAB_DESIGN_SYSTEM.md (deprecated direction — do not reuse). Specs and plans live in docs/superpowers/.

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, 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.

git clone https://git.evandro.dev.br/evandro/HealthTracking.git
cd HealthTracking
npm install
npm run typecheck
npm test -- --runInBand
npm run lint
npm run start      # Metro
npm run android    # run on a device/emulator

Debug APK:

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:

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:

rm -rf android/.gradle android/app/build android/build .gradle
cd android && ./gradlew assembleDebug

Notices

S
Description
No description provided
Readme
4.2 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 66.9%
Kotlin 20.4%
HTML 10.6%
JavaScript 1.3%
Ruby 0.4%
Other 0.4%