Richer Polar sensors + history backfill (full history, not just current data) - #16
Richer Polar sensors + history backfill (full history, not just current data)#16MarcinG81 wants to merge 6 commits into
Conversation
Run a pre-flight check in the user step, before the OAuth flow: verify Home Assistant can reach Polar and that the Client ID is accepted, log the outcome, and re-show the form with a clear cannot_connect / invalid_auth error instead of failing later during the OAuth exchange.
Expose more of the Polar AccessLink data: - Heart rate (continuous, latest sample with min/max/average attributes) - Cardio load (training load) with strain/tolerance/ratio/status - Heart rate variability and Breathing rate from Nightly Recharge - Deep/Light/REM sleep durations (minutes, with an Xh Ym attribute) - Last exercise heart rate average/maximum New AccessLink helpers for continuous heart rate and cardio load; both degrade gracefully on HTTP errors instead of failing the update.
Home Assistant sensors only build history forward and cannot backfill past states. Polar keeps ~28 days of nightly/daily data and per-day continuous heart rate samples, so on setup (and daily afterwards) import that history as long-term statistics attached to each sensor's own entity_id, making the past visible on the entity History page: - sleep stages, sleep score, HRV, breathing rate, cardio load: one point per day for the last ~28 days; - continuous heart rate: hourly min/mean/max for the last 7 days. Sleep stages and sleep score gain state_class=measurement so they generate statistics; adds the recorder dependency. Best-effort import.
3ab0934 to
a8da6d5
Compare
|
thank you @MarcinG81 , I'll test it asap |
On second thought, I'm starting to question the whole "current value" approach here. Polar devices (Loop, watches, etc.) don't stream in real time — they sync to Polar Flow in batches, only every so often. On top of that the data itself is essentially daily-granular (sleep, Nightly Recharge and cardio load are one value per day; continuous heart rate is historical samples). So polling the API every 30 minutes for a "current" reading is pretty much pointless: nothing changes between syncs, and a single 5-minute sample exposed as a live state is honestly a bit misleading. So I'm leaning towards reworking this to be history-first rather than "current value": drop the artificial "current" sensors (at least the live continuous heart rate) and treat the data as what it is — a historical record; Does this direction make sense to you? Happy to refactor along these lines — and I can keep a small set of "latest known value" sensors if you'd still like a snapshot for cards/automations. |
There was a problem hiding this comment.
Pull request overview
This PR enhances the Polar Home Assistant custom integration by exposing additional Polar AccessLink metrics as first-class sensors and introducing a long-term statistics backfill so sensor history is visible for the recent past (not only from install time).
Changes:
- Adds multiple new sensors (continuous heart rate, cardio load, HRV, breathing rate, sleep stages, and last-exercise HR stats) and enriches existing sensor behavior/attributes.
- Implements a long-term statistics history backfill job (run at setup and then daily) to import recent Polar history into Home Assistant’s recorder statistics.
- Improves UX/docs by adding a setup-time connection test, expanding README documentation, and adding a ready-to-use Lovelace dashboard YAML.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
README.md |
Documents new sensors, setup connection test, and history backfill behavior. |
polar-dashboard.yaml |
Adds a sample Lovelace dashboard leveraging statistics-graph for backfilled history. |
custom_components/polar/statistics.py |
New module that fetches Polar history and imports it into long-term statistics. |
custom_components/polar/sensor.py |
Adds new sensor entity descriptions, sleep stage formatting, and richer attributes handling. |
custom_components/polar/polaraccesslink/accesslink.py |
Adds AccessLink helpers for continuous HR and cardio load; flattens exercise HR stats. |
custom_components/polar/manifest.json |
Adds recorder dependency to support statistics backfill. |
custom_components/polar/coordinator.py |
Fetches and exposes cardio load + continuous HR in coordinator data. |
custom_components/polar/const.py |
Introduces new coordinator data keys/constants for cardio load and continuous HR. |
custom_components/polar/config_flow.py |
Adds setup-time pre-flight connection test and form re-render helper. |
custom_components/polar/__init__.py |
Schedules best-effort history import at setup and at a daily interval. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| unique_suffix, | ||
| ) | ||
| continue | ||
| async_import_statistics(hass, _metadata(entity_id, unit), statistics) |
| Home Assistant sensors only build history forward from the moment they start | ||
| polling and cannot backfill past states. Polar however keeps the last ~28 days | ||
| of nightly/daily data and per-day continuous heart rate samples. This module | ||
| imports that history as external long-term statistics (``polar:*``) so it shows | ||
| up in the statistics graphs / Developer Tools, including data from before the | ||
| integration was installed. |
|
Thanks for the review. Two points from the bot:
|
| # VIEW 1 – TODAY (Mushroom, live data) | ||
| # ========================================================================== | ||
| - title: Today | ||
| path: today |
There was a problem hiding this comment.
| path: today | |
| type: sections | |
| path: today |
it would be better to use the new section view, the the integrated badge/tile card that replace mushroom custom card
|
Good call — done in the latest commit. The dashboard now has zero custom-card dependencies:
README updated accordingly (no HACS frontend cards needed). |
Why I put 1h delay between refresh is to be pretty sure to catch all exercices, because we can only fill the |
That's a fair point, and it makes sense for the current model — since only last_exercise is exposed (a single value that gets overwritten), polling often is the only way to make each workout appear as a state change that the recorder / InfluxDB can capture before the next one replaces it. But I'd argue that's actually a reason to treat exercises as history rather than as a "current" sensor. The /exercises endpoint returns a list of recent exercises, not just one — so we can read all of them in a single call, including two or more on the same day, and import them as historical records directly. Done that way the polling cadence stops mattering for not-missing-workouts: each sync pulls the full recent list, so nothing is lost even if you train twice between syncs, or if a sync is hours apart. So I'd split it by data type: Sleep / Nightly Recharge / cardio load / continuous heart rate are daily-granular or historical samples — those genuinely don't need frequent polling; the value only changes once a day (or is backfilled anyway). Does that match how you see it? Happy to prototype the exercise-list import so we can compare. |
|
the sensor value is not the only thing matter for last exercise, in attributes, we have many informations |
You're absolutely right — for last_exercise the state is almost the least interesting part; it's the attributes (sport, distance, duration, HR avg/max, training load, calories...) that carry the value. That's a fair argument against my "import as statistics" idea for exercises specifically: long-term statistics only store numeric mean/min/max, so they'd throw away all of that per-exercise detail — and since they bucket by the hour, two workouts close together could even collapse into one. So I'd treat exercises differently from the daily/continuous metrics. Since /exercises returns the full list with all attributes, a natural fit is to represent each workout as its own record instead of a single overwritten sensor — for example a Calendar entity: one event per exercise (start/end from the session, sport as the title, the remaining attributes in the event details). That keeps every attribute, handles two-or-more workouts a day without overwriting, and — because each sync just re-reads the list and upserts — it doesn't depend on a tight poll interval to avoid missing one. Firing an event per new exercise would additionally cover automations / logbook. So the split would be: daily / nightly / continuous metrics → long-term statistics (history without frequent polling); |
|
Good point — and it actually generalizes beyond exercises: for most of these the value is in the attributes, not the headline number. So I think the cleanest design is a hybrid rather than one-or-the-other:
That keeps every attribute you care about, fixes the "history starts at install" gap, and removes the dependency on a tight poll interval for not-missing-workouts. Best of both worlds. Happy to prototype the exercise calendar + the backfill so you can see it in practice. |
|
I don't like to talk to AI 🤔 |
|
btw don't merge this commits there is critical bug. |






Summary
This makes the Polar integration much more useful by exposing more of the data the Polar AccessLink API provides and, crucially, by backfilling history — so you no longer only see "current" values that start accumulating from install time, but the actual recent past as well.
It bundles three things (happy to split into separate PRs if you prefer):
1. Connection test on setup
After submitting the credentials, the config flow now runs a pre-flight check (reachability to Polar + that the
Client IDis accepted) and logs the outcome, re-showing the form with a clearcannot_connect/invalid_autherror. Previously a wrong URL, no connectivity or a bad client only surfaced later, mid-OAuth, with an unclear failure.2. More sensors
Field names/units taken from the official AccessLink OpenAPI spec:
Xh Ymdurationattribute)strain,tolerance,cardio_load_ratio,cardio_load_statusNew AccessLink helpers for continuous heart rate and cardio load; both degrade gracefully on HTTP errors (unsupported device, missing consent, no data for a day) instead of failing the coordinator update.
3. History backfill — the big usability win
Home Assistant sensors only build history forward from first poll and can't backfill past states. Polar, though, keeps the last ~28 days of nightly/daily data and per-day continuous heart rate samples.
On setup (and once a day after) the integration now imports that history as long-term statistics attached to each sensor's own
entity_id(async_import_statistics, resolving the entity from the registry by unique_id). The result: every sensor's History page shows the real past, including data from before the integration was installed — instead of a graph that only starts now.To make this work, the sleep-stage and sleep-score sensors gained
state_class=measurement, andrecorderwas added as a dependency. The import is best-effort and never fails setup.Bonus: dashboard + docs
polar-dashboard.yaml: a ready-to-use Lovelace dashboard for training + sleep tracking — a Today snapshot (Mushroom) plus Sleep and Training history views (built-instatistics-graph).Notes
Net effect
You go from "I only see data from now on" to a dashboard with the full recent history of sleep, recovery (HRV/recharge), heart rate and training load — genuinely usable for someone training and tracking sleep.