-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
How the integration is put together. Useful if you're contributing (see Development & Releasing) or just curious why things are split the way they are.
Suunto watch ──▶ Suunto app / Sports Tracker cloud ──▶ Home Assistant (this integration)
The integration is cloud polling (iot_class: cloud_polling): it reads the Sports Tracker cloud that your watch syncs into. It never talks to the watch directly.
| Host | Data | Format / auth |
|---|---|---|
api.sports-tracker.com/apiserver/v1/ |
workouts, lifetime stats | ASKO envelope {error, metadata, payload}; header STTAuthorization: <sessionkey>
|
247.sports-tracker.com/ |
sleep, recovery, 24/7 activity | gzipped NDJSON (gunzipped by magic-byte detection); v1/{sleep|recovery|activity}/export?since=<ms>
|
Field-level units and gotchas: Data & Units.
Live values change often and are cheap; history is heavy (~90 days, paginated) and changes slowly. Re-fetching everything on the fast cadence would be wasteful and rough on an unofficial API - so there are two DataUpdateCoordinators:
| Coordinator | Default | Min | Fetches |
|---|---|---|---|
| Fast (live) | 15 min | 5 | the activity stream → current_hr, daily_steps, daily_energy
|
| Daily (history) | 60 min | 15 | sleep (~60 d) + recovery (~5 d) + workouts (~90 d, paginated) + lifetime stats; computes all derived metrics; imports the long-term statistics |
The daily coordinator fetches its sections concurrently (asyncio.gather(..., return_exceptions=True)): one flaky stream leaves that section empty while the others still update; all sections failing → UpdateFailed; an auth error → ConfigEntryAuthFailed (triggers reauth).
entry.runtime_data holds both coordinators. Every sensor description declares a source (fast/daily); setup wires each entity to the right coordinator. All entities (and the calendar) share one device keyed (DOMAIN, entry_id).
Login is a form POST to api/.../login2 with fields l (email), p (password), totp, timestamp, salt, signature. The signing pipeline is ported byte-for-byte from tajchert/suuntool and validated against its golden vectors (login-secret hex, sample signature, PBKDF2, HOTP).
Login returns {sessionkey, username, userKey}:
-
usernamebecomes the config-entry/device title - that's why your device is named after your Suunto handle. -
userKeyis the config-entry unique id (de-dupes the same account). -
sessionkeyis the bearer token used for all subsequent requests; it is cached and reused across restarts.
Key material is extracted from a specific Suunto Android app version. On a major app version bump the embedded constants may need refreshing (the same "key rotation" concept as suuntool). The credential model (password used once, token persisted) is described in Configuration and Privacy & Disclaimer.
| File | Responsibility |
|---|---|
__init__.py |
entry setup/teardown, runtime_data, platform forwarding |
api.py |
HTTP client, ASKO/NDJSON handling, gzip detection, login |
auth.py |
the login signing pipeline (validated against suuntool) |
coordinator.py |
the two coordinators, field mapping, derived-metric assembly, statistics import |
metrics.py |
pure functions: CTL/ATL/TSB, ACWR, baselines, readiness (unit-tested) |
statistics.py |
bucketing + external-statistics import helpers |
sensor.py |
the 56 sensor descriptions |
calendar.py |
the workouts calendar entity |
config_flow.py |
login + reauth + options (poll intervals) |
const.py |
constants, look-back windows, activityId map |
- Platforms:
["sensor", "calendar"]. - Minimum HA: 2024.12.0. Uses
entry.runtime_data(2024.5+),DataUpdateCoordinator(config_entry=...)(2024.8+), modern reauth helpers (2024.8+), theOptionsFlowconfig_entryproperty (2024.11+), and PEP 695typealiases (Py 3.12). -
manifest.jsondeclaresafter_dependencies: ["recorder"]for the statistics import.
ha-suunto - Unofficial Suunto integration for Home Assistant - Not affiliated with Suunto / Amer Sports / Sports-Tracker - Report an issue
Getting started
Reference
Build with it
Under the hood
Help