|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Communication style |
| 6 | + |
| 7 | +This user prefers caveman mode: terse, no filler, fragments OK, short synonyms. Drop articles/hedging/pleasantries. Technical terms exact. Code unchanged. Pattern: `[thing] [action] [reason]. [next step].` |
| 8 | + |
| 9 | +## Commands |
| 10 | + |
| 11 | +```bash |
| 12 | +scripts/setup # Install dependencies and pre-commit hooks |
| 13 | +scripts/test # Run pytest with coverage (pass extra args after --) |
| 14 | +scripts/lint # Run pre-commit + vulture dead-code check |
| 15 | +scripts/develop # Start local Home Assistant instance with the integration loaded |
| 16 | +``` |
| 17 | + |
| 18 | +Run a single test file: |
| 19 | +```bash |
| 20 | +pytest tests/test_sensor.py -v |
| 21 | +``` |
| 22 | + |
| 23 | +## Architecture |
| 24 | + |
| 25 | +This is a Home Assistant custom integration that polls the Garmin Connect cloud API via the [`ha-garmin`](https://github.com/cyberjunky/ha-garmin) library. The library is the only Garmin API dependency — all data fetching happens there. |
| 26 | + |
| 27 | +### Data flow |
| 28 | + |
| 29 | +Each data domain has its own `DataUpdateCoordinator` subclass in [coordinator.py](custom_components/garmin_connect/coordinator.py). All coordinators share the same `GarminClient` and `GarminAuth` instances, and each calls one `client.fetch_*_data()` method per poll: |
| 30 | + |
| 31 | +| Coordinator | Data | |
| 32 | +|---|---| |
| 33 | +| `CoreCoordinator` | Daily summary, steps, sleep, HR, stress, SpO2, body battery (~50 sensors) | |
| 34 | +| `ActivityCoordinator` | Last activity, last 10 activities, polyline, workouts (~5 sensors) | |
| 35 | +| `TrainingCoordinator` | Readiness, VO2max, HRV, training status, scores (~11 sensors) | |
| 36 | +| `BodyCoordinator` | Weight, BMI, hydration, fitness age, body composition (~17 sensors) | |
| 37 | +| `GoalsCoordinator` | Badges, points, active goals (~6 sensors) | |
| 38 | +| `GearCoordinator` | Gear stats (dynamic sensors per item), alarms | |
| 39 | +| `BloodPressureCoordinator` | Latest BP reading (~3 sensors) | |
| 40 | +| `MenstrualCoordinator` | Menstrual data (~9 sensors, disabled by default) | |
| 41 | + |
| 42 | +### Sensor definitions |
| 43 | + |
| 44 | +All sensors are declared as `GarminConnectSensorEntityDescription` tuples in [sensor.py](custom_components/garmin_connect/sensor.py), grouped by coordinator. Each description has: |
| 45 | +- `coordinator_type` — which coordinator feeds it |
| 46 | +- `value_fn` — lambda to extract the state value from coordinator data (falls back to `key` lookup if omitted) |
| 47 | +- `attributes_fn` — lambda to extract extra state attributes |
| 48 | +- `preserve_value=True` — retains last non-`None` value (used for weight, sleep, HRV which go `None` mid-day) |
| 49 | + |
| 50 | +### Key data facts from `ha-garmin` |
| 51 | + |
| 52 | +- `startTimeLocal` is **dropped** by the library; use `startTime` (UTC datetime) instead. In templates: `(a.startTime | as_datetime | as_local).strftime('%Y-%m-%d')` |
| 53 | +- `activityType` is simplified to a plain string (`"running"`, `"cycling"`, etc.) |
| 54 | +- `polyline` (GPS coordinates as `[{lat, lon}]`) lives on `lastActivityRoute` sensor, **not** `lastActivity` |
| 55 | + |
| 56 | +### Custom Lovelace card |
| 57 | + |
| 58 | +`www/garmin-polyline-card.js` renders activity routes using Leaflet. Users must copy **all three files** from `www/` to `<config>/www/`: the card JS plus `leaflet.js` and `leaflet.css`. |
| 59 | + |
| 60 | +### Entity unique IDs |
| 61 | + |
| 62 | +Format: `{entry_id}_{sensor_key}`. The v1→v2 migration in [\_\_init\_\_.py](custom_components/garmin_connect/__init__.py) rewrites unique IDs from the old `{email}_{key}` format and triggers reauth (tokens are incompatible between versions). |
0 commit comments