# Fitbit Sense 2 ↔ Hevy Live Workout Companion — Project Handoff

**Date:** 2026-05-27
**Author:** Pipo (Hermes Agent)
**For:** ChatGPT 5.5 (or any capable AI agent)

---

## 1. OBJECTIVE

Build a **Fitbit Sense 2 app** that acts as a **live, interactive workout companion** for Hevy.

### Required Features (user's exact words)
1. See what exercise is currently active on the Fitbit
2. See how many reps and what weight for the current set
3. Mark exercise/set as DONE from the Fitbit (which activates the rest timer)
4. Show rest timer countdown on the Fitbit
5. Skip rest, or add/subtract 15 seconds from rest timer
6. **CRITICAL:** Every action taken on the Fitbit must reflect LIVE on the Hevy app on the phone (bidirectional sync — not just read-only notifications)

### User
- **Name:** Ignacio "Chicho/Igna" Lagos Ruiz
- **Location:** Argentina (UTC-3)
- **Phone:** Pixel 9a (Android, Tailscale IP: 100.111.239.9)
- **Watch:** Fitbit Sense 2 (codename `rhea` in Fitbit SDK)
- **Hevy:** Pro subscription, API key available in Bitwarden
- **Personality:** Direct, zero patience for filler, wants autonomous execution. Prefers clear instructions when interaction is unavoidable.

---

## 2. INFRASTRUCTURE

### Server
- **Host:** Oracle Cloud ARM64 (4 CPUs, 24GB RAM), Ubuntu 24.04
- **Hostname:** miopenclaw-vnic
- **Tailscale IP:** 100.87.116.90
- **Tailscale Funnel:** https://miopenclaw-vnic.tail9799d2.ts.net (port 8443 → proxy to 127.0.0.1:3017)
- **Working directory:** `/home/ubuntu/`

### Phone Connection
- **ADB:** Wireless debugging over Tailscale at `100.111.239.9:36353`
- **Termux:** Installed on phone (`com.termux`)
- **OpenClaw:** Installed on phone, connected to gateway at `ws://100.87.116.90:18789`
- **Hevy package:** `com.hevy` (React Native app)
- **Fitbit app:** `com.fitbit.FitbitMobile`

### Key Tools Installed on Server
- Python 3.11 with FastAPI + uvicorn (in `/home/ubuntu/.hermes/hermes-agent/venv/`)
- Node.js v22
- ADB (android-tools-adb)
- Gradle (`/usr/local/bin/gradle`)
- Fitbit SDK 7.2.0-pre.0 + cmengler build targets (for rhea/hera)

---

## 3. ARCHITECTURE

```
┌─────────────────────┐     peerSocket (BT)    ┌──────────────────────┐
│  Fitbit Sense 2     │ ◄───────────────────► │  Fitbit Companion    │
│  (rhea, watch app)  │                        │  (in Fitbit phone    │
│                     │                        │   app's WebView)     │
│  - UI: exercise     │                        │                      │
│    name, set X/Y,   │                        │  - Polls backend     │
│    weight × reps,   │                        │    every 2s          │
│    rest timer       │                        │  - Forwards state    │
│  - Buttons: DONE,   │                        │    to watch          │
│    SKIP, -15, +15   │                        │  - Sends watch       │
│  - Haptics on       │                        │    actions to backend│
│    set done, timer   │                        │                      │
│    end               │                        │                      │
└─────────────────────┘                        └──────────┬───────────┘
                                                          │ HTTP fetch()
                                                          │ (Tailscale)
                                               ┌──────────▼───────────┐
                                               │   Pipo Backend       │
                                               │   (FastAPI :8420)    │
                                               │                      │
                                               │ GET /api/hevy/live   │
                                               │   → ADB screenshot   │
                                               │   → parse UI state   │
                                               │   → return JSON      │
                                               │                      │
                                               │ POST /api/hevy/action│
                                               │   → ADB tap/input    │
                                               │   → control Hevy     │
                                               └──────────┬───────────┘
                                                          │ ADB over Tailscale
                                               ┌──────────▼───────────┐
                                               │   Pixel 9a Phone     │
                                               │                      │
                                               │  Hevy app (foreground│
                                               │  during workout)     │
                                               └──────────────────────┘
```

### Data Flow
1. **Backend** takes ADB screenshots of Hevy app every 2 seconds
2. Parses screenshot with vision (Gemini/OpenAI) to extract: exercise name, set number, total sets, weight, reps, rest timer, phase
3. **Companion** polls `GET /api/hevy/live` every 2s, sends state to watch via `peerSocket`
4. **Watch** renders exercise info or rest timer based on phase
5. User taps button on watch → **Companion** sends `POST /api/hevy/action` to backend
6. **Backend** executes ADB commands on phone to control Hevy (tap buttons, etc.)

### State Model
```json
{
  "phase": "exercise" | "rest" | "idle" | "loading",
  "exercise": "Bench Press",
  "setCurrent": 2,
  "setTotal": 4,
  "weight": "80 kg",
  "reps": "8",
  "restSeconds": 90,
  "duration": "23min 15s",
  "volume": "3200 kg",
  "timestamp": 1779900000.0
}
```

---

## 4. CURRENT STATUS — WHAT'S DONE

### 4.1 Fitbit App (Watch + Companion) — `✅ BUILT, NEEDS INSTALL`

**Location:** `/home/ubuntu/fitbit-hevy-spike/path-a-companion/`

**Files:**
- `app/index.js` — Watch app UI (exercise, timer, buttons) + FSM state machine
- `companion/index.js` — Polls backend every 2s, forwards state to watch, sends actions to backend
- `resources/index.view` — SVG-based UI layout for Sense 2 (dark theme, orange/cyan accents)
- `resources/styles.css` — Styling
- `resources/widget.defs` — System widget imports
- `package.json` — Fitbit SDK config: targets `rhea` (Sense 2), UUID `2d6f8a3e-b9c1-4a7d-8e5f-0c2b4a6d9f13`

**Built .fba:** `/home/ubuntu/fitbit-hevy-spike/path-a-companion/build/app.fba` (9390 bytes, BuildID: 0x078e2d97ba746256)

**Installation:** The .fba needs to be uploaded to GAM (Gallery App Manager) at https://gam.fitbit.com/ as a private app. Chicho's developer account is active. The app was created as draft "Hevy Companion". The .fba needs to be uploaded in "Manage versions" section, then "Publish App" to get a private link. Chicho then opens the private link on his phone → Fitbit Gallery → Install.

**⚠️ The companion hardcodes `http://100.87.116.90:8420` as backend URL. This MUST be changed if the backend IP/port changes.**

### 4.2 Backend Server — `✅ RUNNING (partial)`

**Location:** `/home/ubuntu/hevy-companion-backend/server.py` (FastAPI)

**Process:** Running on PID shown by `ps aux | grep server.py` on port 8420

**Endpoints:**
- `GET /` → health check
- `GET /api/hevy/live` → returns current Hevy state (currently hardcoded placeholder)
- `POST /api/hevy/action` → NOT YET IMPLEMENTED (needs to execute ADB actions)
- `GET /download/app.fba` → serves the .fba file

**Tailscale serve:** `https://miopenclaw-vnic.tail9799d2.ts.net/hevy-api/` → proxy to `http://127.0.0.1:8420`

**⚠️ The backend currently returns HARDCODED placeholder data. The vision parsing pipeline is NOT implemented.**

### 4.3 Hevy Live State Research — `✅ ANALYZED`

**Hevy foreground service notification:**
- Package: `com.hevy`
- Channel: `FOREGROUND_SERVICE_NOTIFICATION_CHANNEL_ID` ("Rest Timer")
- Flags: ONGOING_EVENT | NO_CLEAR | FOREGROUND_SERVICE | LOCAL_ONLY | SILENT
- Uses custom RemoteViews (text not in standard extras)
- Action buttons: "Skip", "-15s", "+15s" (via broadcast intents — exact action strings unknown)
- Rest timer visible in notification

**Hevy UI state (from screenshot analysis):**
- Shows exercise name, set number, weight/reps for weight exercises, timed duration for warm-ups
- Rest timer with countdown
- Checkmark button to mark set complete
- "+ Add Set" and "+ Add Exercise" buttons
- "Discard Workout" and "Settings"

**AsyncStorage:**
- File: `/data/data/com.hevy/databases/RKStorage` (SQLite)
- Hevy is NOT debuggable (`run-as` fails), so direct DB access requires root or backup
- This is the definitive source of workout state but inaccessible without root

**APK:**
- React Native bundle extracted at `/tmp/hevy-apk/extract/index.android.bundle` (22MB)
- Search for workout-related strings in this bundle for internal API endpoints

### 4.4 Spike Research Docs — `✅ COMPREHENSIVE`

**Location:** `/home/ubuntu/fitbit-hevy-spike/`

```
├── README.md                               # Overview
├── OPTIONS-RACE-SUMMARY.md                 # Three-path comparison
├── path-a-companion/                       # Fitbit SDK app (THE CHOSEN PATH)
│   ├── ARCHITECTURE.md                     # Full architecture doc
│   ├── SMOKE-TEST.md                       # DevBridge setup guide
│   └── package-choices.md                  # SDK version decisions
├── path-b-hevy-live/                       # Hevy reverse-engineering research
│   ├── adb-extraction-commands.md          # ADB data extraction guide
│   ├── apk-analysis-guide.md              # APK decompile instructions
│   ├── internal-api-endpoints.md          # Known Hevy internal API endpoints
│   ├── network-capture-guide.md           # MITM proxy setup
│   └── capture-hevy-android-state.sh      # ADB snapshot script
└── path-c-android-bridge/                  # Phone-side bridge research
    ├── 01-hevy-integration-surfaces.md     # Hevy Android integration points
    ├── 02-fitbit-companion-constraints.md  # What Fitbit companion can/can't do
    ├── 03-bridge-approaches.md            # Notification vs Accessibility vs Tasker
    ├── 06-prototype-plan.md               # Prototype implementation plan
    └── LIVE-FINDINGS.md                   # Hevy notification live analysis
```

### 4.5 Hevy API Integration — `✅ WORKING`

**CLI:** `hevy` command available (wrapper at `~/.local/bin/hevy`)
**API Key:** In Bitwarden (`HEVY_API_KEY` env var)
**Key endpoints for sync:**
- `GET /v1/routines/{id}` — fetch workout plan
- `POST /v1/workouts` — create completed workout
- `GET /v1/exercise_templates` — exercise catalog

---

## 5. WHAT'S MISSING — NEXT STEPS

### 5.1 CRITICAL: Install .fba on Sense 2
1. Chicho needs to download the .fba from `https://miopenclaw-vnic.tail9799d2.ts.net/hevy-api/download/app.fba`
2. In GAM (https://gam.fitbit.com/), on the "Hevy Companion" draft app page, scroll to "Manage versions"
3. Upload the .fba, set version "0.2.0", check the rights box, click "Publish App"
4. GAM provides a private link — open it on the phone → Fitbit Gallery → Install
5. **Verify:** The app should appear on the Sense 2. Launch it — it will show "LOADING" until backend is live

### 5.2 HIGH: Implement ADB-based Hevy State Reading
The companion needs real data. Currently the backend returns hardcoded placeholders.

**Approach A — Screenshot + Vision (recommended):**
```python
# In server.py, update /api/hevy/live:
def get_hevy_state():
    # 1. Take screenshot via ADB
    subprocess.run(["adb", "exec-out", "screencap", "-p"], 
                   capture_output=True)
    # 2. Send to vision API (Gemini Flash / OpenAI Vision)
    # 3. Parse response into state JSON
    # 4. Return state
```
- Pros: Works regardless of Hevy UI changes
- Cons: 1-2s latency, costs vision API tokens (~$0.0001 per screenshot)
- Use Gemini Flash 2.0 for speed + cost

**Approach B — Accessibility Tree (fallback):**
```bash
adb shell "uiautomator dump --compressed /sdcard/ui.xml"
adb shell cat /sdcard/ui.xml
```
- The `--compressed` flag works around the "not idle" error caused by the animated rest timer
- Parse XML to extract text elements
- Must ensure Hevy is in foreground

### 5.3 HIGH: Implement ADB-based Hevy Control
The companion POSTs `/api/hevy/action` with `{action: "set_done"|"skip_rest"|"timer_adjust", delta: ±15}`. These need to control Hevy on the phone.

**For tap actions (set_done, skip_rest):**
```bash
# Tap the checkmark/DONE button (coordinates from screenshot analysis)
adb shell input tap X Y

# Or try to broadcast (if intent actions are discovered)
adb shell am broadcast -n com.hevy/.SomeReceiver -a SOME_ACTION
```

**Finding coordinates:**
1. Take screenshot during active workout
2. Use vision API to locate the "checkmark" or "Finish" button
3. Get bounding box coordinates
4. Tap center of bounding box

**For timer adjust:**
- The notification has broadcast intents for -15s and +15s
- Need to discover the exact intent action strings
- Can extract from APK bundle: search `/tmp/hevy-apk/extract/index.android.bundle` for "skip_rest", "timer_adjust", "add_15_seconds" etc.

### 5.4 MEDIUM: Keep Hevy in Foreground
The ADB-based approach requires Hevy to be in the foreground (screen on, app visible).

**Options:**
1. Set screen timeout to max: `adb shell settings put system screen_off_timeout 1800000` (30 min)
2. Use `adb shell input keyevent KEYCODE_WAKEUP` to wake screen before screenshots
3. Use `adb shell am start -n com.hevy/.MainActivity` to bring Hevy to front

### 5.5 MEDIUM: Backend Auto-Start
Make the backend server start automatically:
```bash
# As a systemd service or cron @reboot
# Currently started manually with:
/home/ubuntu/.hermes/hermes-agent/venv/bin/python3 /home/ubuntu/hevy-companion-backend/server.py
```

### 5.6 LOW: Hevy Internal API Discovery
The moonshot: find Hevy's live workout API.

**Search targets:**
- `/tmp/hevy-apk/extract/index.android.bundle` — React Native bundle (22MB)
  ```bash
  grep -oP '"https?://[^"]*"' index.android.bundle | sort -u
  grep -i "workout\|live\|session\|sync\|websocket" index.android.bundle
  ```
- Hevy web app DevTools (open Hevy in Chrome, start workout, check Network tab)
- Known community work: `dmzoneill/hevyapp-api` on GitHub
- Headers: `klean_kanteen_insulated`, `shelobs_hevy_web`

### 5.7 LOW: Hevy AsyncStorage Access
If phone can be rooted or backup enabled:
```bash
# Check if allowBackup is true
adb shell dumpsys package com.hevy | grep allowBackup

# If yes:
adb backup -f hevy.ab com.hevy
# Extract RKStorage SQLite database
# Query for workout state keys
```

---

## 6. CRITICAL CONSTRAINTS & GOTCHAS

### Sense 2 Developer Bridge
- **Requires:** Phone's Fitbit app → Developer Menu → Developer Bridge ON
- **Requires:** Sense 2 on charger → Settings → Developer Bridge → USB Debugging ON
- **Requires:** Fitbit developer account (login at gam.fitbit.com, accept ToS)
- The DevBridge stays active for ~30 min, then may disconnect
- If "Waiting for Studio" disappears, re-toggle Developer Bridge in Fitbit app

### Fitbit SDK for Sense 2
- Uses unofficial/abandoned pre-release SDK: `@fitbit/sdk@7.2.0-pre.0`
- Build targets from `cmengler/fitbit-sdk-build-targets`
- `rhea` = Sense 2, `hera` = Versa 4
- Build command: `FITBIT_QA_COMMANDS=1 npx fitbit-build`
- The SDK builds but `npx fitbit` (interactive CLI) requires OAuth browser login — not possible on headless server
- **GAM upload is the practical installation path**

### Fitbit Companion Limitations
- The companion runs in the Fitbit phone app's WebView
- Can make `fetch()` calls to the internet
- Phone is on Tailscale, so `100.87.116.90:8420` should be reachable
- **The companion shares the phone's network stack, not the watch's**

### ADB Timing
- Screenshots take ~1-2s over wireless ADB
- Hevy UI updates every second (rest timer countdown)
- Poll interval of 2-3s is reasonable
- Must handle ADB disconnections gracefully

---

## 7. QUICK COMMANDS REFERENCE

```bash
# ADB
adb connect 100.111.239.9:36353
adb devices
adb shell uiautomator dump --compressed /sdcard/ui.xml  # accessibility tree
adb exec-out screencap -p > screenshot.png               # screenshot
adb shell input tap X Y                                   # tap
adb shell input keyevent KEYCODE_WAKEUP                   # wake screen
adb shell am start -n com.hevy/.MainActivity              # open Hevy

# Backend
curl http://localhost:8420/api/hevy/live
/home/ubuntu/.hermes/hermes-agent/venv/bin/python3 /home/ubuntu/hevy-companion-backend/server.py

# Fitbit build
cd /home/ubuntu/fitbit-hevy-spike/path-a-companion
FITBIT_QA_COMMANDS=1 npx fitbit-build

# Hevy API
hevy status
hevy workouts --kg --limit 5
hevy routines

# Tailscale
tailscale serve status
tailscale funnel status
```

---

## 8. IMMEDIATE ACTION PLAN

1. **Tell Chicho to download and upload the .fba to GAM** — the file is at the URL above
2. **Implement the vision-based Hevy state parser** — integrate Gemini Flash API into server.py
3. **Implement ADB tap actions** — find screen coordinates for set_done, skip, timer buttons
4. **Test end-to-end** — start Hevy workout on phone, verify watch shows exercise, test button taps
5. **Iterate on UI** — the current watch UI is minimal; improve layout, add exercise icon, better colors
