mirror of
https://github.com/cyberjunky/home-assistant-garmin_connect.git
synced 2026-01-09 12:57:58 -05:00
Added HRV and weekly steps sensors
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
# Garmin Connect
|
# Garmin Connect
|
||||||
|
|
||||||
Integrate your Garmin Connect fitness data with Home Assistant. Access **100+ sensors** covering health metrics, activities, body composition, and gear tracking—plus add measurements directly to Garmin Connect via services.
|
Integrate your Garmin Connect fitness data with Home Assistant. Access **110+ sensors** covering health metrics, activities, body composition, and gear tracking—plus add measurements directly to Garmin Connect via services.
|
||||||
|
|
||||||
## Supported Features
|
## Supported Features
|
||||||
|
|
||||||
@@ -29,6 +29,8 @@ Sensor values depend on your Garmin devices and connected apps.
|
|||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| Total Steps | Daily step count |
|
| Total Steps | Daily step count |
|
||||||
| Daily Step Goal | Your configured step target |
|
| Daily Step Goal | Your configured step target |
|
||||||
|
| Yesterday Steps/Distance | Previous day's complete totals |
|
||||||
|
| Weekly Step/Distance Avg | 7-day averages |
|
||||||
| Total Distance | Distance walked/run in meters |
|
| Total Distance | Distance walked/run in meters |
|
||||||
| Floors Ascended/Descended | Floors climbed |
|
| Floors Ascended/Descended | Floors climbed |
|
||||||
|
|
||||||
@@ -46,6 +48,8 @@ Sensor values depend on your Garmin devices and connected apps.
|
|||||||
| Resting Heart Rate | Daily resting HR |
|
| Resting Heart Rate | Daily resting HR |
|
||||||
| Min/Max Heart Rate | Daily HR range |
|
| Min/Max Heart Rate | Daily HR range |
|
||||||
| Last 7 Days Avg HR | Weekly average |
|
| Last 7 Days Avg HR | Weekly average |
|
||||||
|
| HRV Weekly/Nightly Avg | Heart rate variability metrics |
|
||||||
|
| HRV Baseline | Personal HRV baseline |
|
||||||
|
|
||||||
### Stress & Recovery
|
### Stress & Recovery
|
||||||
|
|
||||||
@@ -60,6 +64,9 @@ Sensor values depend on your Garmin devices and connected apps.
|
|||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| Sleep Score | Overall sleep quality score |
|
| Sleep Score | Overall sleep quality score |
|
||||||
| Sleep/Awake Duration | Time asleep and awake |
|
| Sleep/Awake Duration | Time asleep and awake |
|
||||||
|
| Deep Sleep | Time in deep sleep |
|
||||||
|
| Light Sleep | Time in light sleep |
|
||||||
|
| REM Sleep | Time in REM sleep |
|
||||||
|
|
||||||
### Body Battery
|
### Body Battery
|
||||||
|
|
||||||
|
|||||||
@@ -96,9 +96,18 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
gear_defaults = {}
|
gear_defaults = {}
|
||||||
activity_types = {}
|
activity_types = {}
|
||||||
last_activities = []
|
last_activities = []
|
||||||
|
daily_steps: list[dict[str, Any]] = []
|
||||||
|
yesterday_steps = None
|
||||||
|
yesterday_distance = None
|
||||||
|
weekly_step_avg = None
|
||||||
|
weekly_distance_avg = None
|
||||||
sleep_data = {}
|
sleep_data = {}
|
||||||
sleep_score = None
|
sleep_score = None
|
||||||
sleep_time_seconds = None
|
sleep_time_seconds = None
|
||||||
|
deep_sleep_seconds = None
|
||||||
|
light_sleep_seconds = None
|
||||||
|
rem_sleep_seconds = None
|
||||||
|
awake_sleep_seconds = None
|
||||||
hrv_data = {}
|
hrv_data = {}
|
||||||
hrv_status = {"status": "unknown"}
|
hrv_status = {"status": "unknown"}
|
||||||
endurance_data = {}
|
endurance_data = {}
|
||||||
@@ -113,6 +122,29 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
self.api.get_user_summary, today.isoformat()
|
self.api.get_user_summary, today.isoformat()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Fetch last 7 days steps for weekly average and yesterday's final count
|
||||||
|
week_ago = (today - timedelta(days=7)).isoformat()
|
||||||
|
yesterday = (today - timedelta(days=1)).isoformat()
|
||||||
|
daily_steps = await self.hass.async_add_executor_job(
|
||||||
|
self.api.get_daily_steps, week_ago, yesterday
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process daily steps for yesterday values and weekly averages
|
||||||
|
if daily_steps:
|
||||||
|
# Yesterday is the last item in the list
|
||||||
|
if daily_steps:
|
||||||
|
yesterday_data = daily_steps[-1]
|
||||||
|
yesterday_steps = yesterday_data.get("totalSteps")
|
||||||
|
yesterday_distance = yesterday_data.get("totalDistance")
|
||||||
|
|
||||||
|
# Calculate weekly averages
|
||||||
|
total_steps = sum(d.get("totalSteps", 0) for d in daily_steps)
|
||||||
|
total_distance = sum(d.get("totalDistance", 0) for d in daily_steps)
|
||||||
|
days_count = len(daily_steps)
|
||||||
|
if days_count > 0:
|
||||||
|
weekly_step_avg = round(total_steps / days_count)
|
||||||
|
weekly_distance_avg = round(total_distance / days_count)
|
||||||
|
|
||||||
body = await self.hass.async_add_executor_job(
|
body = await self.hass.async_add_executor_job(
|
||||||
self.api.get_body_composition, today.isoformat()
|
self.api.get_body_composition, today.isoformat()
|
||||||
)
|
)
|
||||||
@@ -163,13 +195,11 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
menstrual_data = await self.hass.async_add_executor_job(
|
menstrual_data = await self.hass.async_add_executor_job(
|
||||||
self.api.get_menstrual_data_for_date, today.isoformat()
|
self.api.get_menstrual_data_for_date, today.isoformat()
|
||||||
)
|
)
|
||||||
_LOGGER.debug("Menstrual data: %s", menstrual_data)
|
|
||||||
# API returns None when not enabled - convert to empty dict
|
# API returns None when not enabled - convert to empty dict
|
||||||
if menstrual_data is None:
|
if menstrual_data is None:
|
||||||
menstrual_data = {}
|
menstrual_data = {}
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
# Menstrual data not available for this user
|
# Menstrual data not available for this user
|
||||||
_LOGGER.debug("Menstrual data error: %s", err)
|
|
||||||
menstrual_data = {}
|
menstrual_data = {}
|
||||||
|
|
||||||
except (
|
except (
|
||||||
@@ -247,6 +277,26 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
deep_sleep_seconds = sleep_data["dailySleepDTO"]["deepSleepSeconds"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
light_sleep_seconds = sleep_data["dailySleepDTO"]["lightSleepSeconds"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
rem_sleep_seconds = sleep_data["dailySleepDTO"]["remSleepSeconds"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
awake_sleep_seconds = sleep_data["dailySleepDTO"]["awakeSleepSeconds"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if hrv_data and "hrvSummary" in hrv_data:
|
if hrv_data and "hrvSummary" in hrv_data:
|
||||||
hrv_status = hrv_data["hrvSummary"]
|
hrv_status = hrv_data["hrvSummary"]
|
||||||
@@ -269,6 +319,14 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
"gearDefaults": gear_defaults,
|
"gearDefaults": gear_defaults,
|
||||||
"sleepScore": sleep_score,
|
"sleepScore": sleep_score,
|
||||||
"sleepTimeSeconds": sleep_time_seconds,
|
"sleepTimeSeconds": sleep_time_seconds,
|
||||||
|
"deepSleepSeconds": deep_sleep_seconds,
|
||||||
|
"lightSleepSeconds": light_sleep_seconds,
|
||||||
|
"remSleepSeconds": rem_sleep_seconds,
|
||||||
|
"awakeSleepSeconds": awake_sleep_seconds,
|
||||||
|
"yesterdaySteps": yesterday_steps,
|
||||||
|
"yesterdayDistance": yesterday_distance,
|
||||||
|
"weeklyStepAvg": weekly_step_avg,
|
||||||
|
"weeklyDistanceAvg": weekly_distance_avg,
|
||||||
"hrvStatus": hrv_status,
|
"hrvStatus": hrv_status,
|
||||||
"enduranceScore": endurance_status,
|
"enduranceScore": endurance_status,
|
||||||
**fitnessage_data,
|
**fitnessage_data,
|
||||||
|
|||||||
@@ -49,6 +49,36 @@ ACTIVITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
|
|||||||
native_unit_of_measurement="steps",
|
native_unit_of_measurement="steps",
|
||||||
icon="mdi:target",
|
icon="mdi:target",
|
||||||
),
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="yesterdaySteps",
|
||||||
|
translation_key="yesterday_steps",
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
native_unit_of_measurement="steps",
|
||||||
|
icon="mdi:walk",
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="weeklyStepAvg",
|
||||||
|
translation_key="weekly_step_avg",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement="steps",
|
||||||
|
icon="mdi:chart-line",
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="yesterdayDistance",
|
||||||
|
translation_key="yesterday_distance",
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
native_unit_of_measurement=UnitOfLength.METERS,
|
||||||
|
icon="mdi:map-marker-distance",
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="weeklyDistanceAvg",
|
||||||
|
translation_key="weekly_distance_avg",
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement=UnitOfLength.METERS,
|
||||||
|
icon="mdi:chart-line",
|
||||||
|
),
|
||||||
GarminConnectSensorEntityDescription(
|
GarminConnectSensorEntityDescription(
|
||||||
key="totalDistanceMeters",
|
key="totalDistanceMeters",
|
||||||
translation_key="total_distance",
|
translation_key="total_distance",
|
||||||
@@ -168,6 +198,40 @@ HEART_RATE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
|
|||||||
attributes_fn=lambda data: {k: v for k, v in data.get(
|
attributes_fn=lambda data: {k: v for k, v in data.get(
|
||||||
"hrvStatus", {}).items() if k != "status"} if data.get("hrvStatus") else {},
|
"hrvStatus", {}).items() if k != "status"} if data.get("hrvStatus") else {},
|
||||||
),
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="hrvWeeklyAvg",
|
||||||
|
translation_key="hrv_weekly_avg",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement="ms",
|
||||||
|
icon="mdi:heart-pulse",
|
||||||
|
value_fn=lambda data: data.get("hrvStatus", {}).get("weeklyAvg"),
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="hrvLastNightAvg",
|
||||||
|
translation_key="hrv_last_night_avg",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement="ms",
|
||||||
|
icon="mdi:heart-pulse",
|
||||||
|
value_fn=lambda data: data.get("hrvStatus", {}).get("lastNightAvg"),
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="hrvLastNight5MinHigh",
|
||||||
|
translation_key="hrv_last_night_5min_high",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement="ms",
|
||||||
|
icon="mdi:heart-pulse",
|
||||||
|
value_fn=lambda data: data.get("hrvStatus", {}).get("lastNight5MinHigh"),
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="hrvBaseline",
|
||||||
|
translation_key="hrv_baseline",
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
native_unit_of_measurement="ms",
|
||||||
|
icon="mdi:heart-pulse",
|
||||||
|
value_fn=lambda data: data.get("hrvStatus", {}).get("baseline", {}).get(
|
||||||
|
"lowUpper") if data.get("hrvStatus", {}).get("baseline") else None,
|
||||||
|
attributes_fn=lambda data: data.get("hrvStatus", {}).get("baseline", {}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stress Sensors
|
# Stress Sensors
|
||||||
@@ -304,6 +368,46 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
|
|||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
icon="mdi:sleep",
|
icon="mdi:sleep",
|
||||||
),
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="deepSleepSeconds",
|
||||||
|
translation_key="deep_sleep",
|
||||||
|
device_class=SensorDeviceClass.DURATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
|
icon="mdi:sleep",
|
||||||
|
value_fn=lambda data: round(data.get(
|
||||||
|
"deepSleepSeconds", 0) / 60, 2) if data.get("deepSleepSeconds") else None,
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="lightSleepSeconds",
|
||||||
|
translation_key="light_sleep",
|
||||||
|
device_class=SensorDeviceClass.DURATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
|
icon="mdi:sleep",
|
||||||
|
value_fn=lambda data: round(data.get(
|
||||||
|
"lightSleepSeconds", 0) / 60, 2) if data.get("lightSleepSeconds") else None,
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="remSleepSeconds",
|
||||||
|
translation_key="rem_sleep",
|
||||||
|
device_class=SensorDeviceClass.DURATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
|
icon="mdi:sleep",
|
||||||
|
value_fn=lambda data: round(data.get(
|
||||||
|
"remSleepSeconds", 0) / 60, 2) if data.get("remSleepSeconds") else None,
|
||||||
|
),
|
||||||
|
GarminConnectSensorEntityDescription(
|
||||||
|
key="awakeSleepSeconds",
|
||||||
|
translation_key="awake_sleep",
|
||||||
|
device_class=SensorDeviceClass.DURATION,
|
||||||
|
state_class=SensorStateClass.TOTAL,
|
||||||
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
|
icon="mdi:sleep-off",
|
||||||
|
value_fn=lambda data: round(data.get(
|
||||||
|
"awakeSleepSeconds", 0) / 60, 2) if data.get("awakeSleepSeconds") else None,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Body Battery Sensors
|
# Body Battery Sensors
|
||||||
|
|||||||
@@ -43,6 +43,18 @@
|
|||||||
"daily_step_goal": {
|
"daily_step_goal": {
|
||||||
"name": "Daily step goal"
|
"name": "Daily step goal"
|
||||||
},
|
},
|
||||||
|
"yesterday_steps": {
|
||||||
|
"name": "Yesterday steps"
|
||||||
|
},
|
||||||
|
"weekly_step_avg": {
|
||||||
|
"name": "Weekly step average"
|
||||||
|
},
|
||||||
|
"yesterday_distance": {
|
||||||
|
"name": "Yesterday distance"
|
||||||
|
},
|
||||||
|
"weekly_distance_avg": {
|
||||||
|
"name": "Weekly distance average"
|
||||||
|
},
|
||||||
"total_distance": {
|
"total_distance": {
|
||||||
"name": "Total distance"
|
"name": "Total distance"
|
||||||
},
|
},
|
||||||
@@ -103,6 +115,18 @@
|
|||||||
"hrv_status": {
|
"hrv_status": {
|
||||||
"name": "HRV status"
|
"name": "HRV status"
|
||||||
},
|
},
|
||||||
|
"hrv_weekly_avg": {
|
||||||
|
"name": "HRV weekly average"
|
||||||
|
},
|
||||||
|
"hrv_last_night_avg": {
|
||||||
|
"name": "HRV last night average"
|
||||||
|
},
|
||||||
|
"hrv_last_night_5min_high": {
|
||||||
|
"name": "HRV last night 5-min high"
|
||||||
|
},
|
||||||
|
"hrv_baseline": {
|
||||||
|
"name": "HRV baseline"
|
||||||
|
},
|
||||||
"avg_stress_level": {
|
"avg_stress_level": {
|
||||||
"name": "Avg stress level"
|
"name": "Avg stress level"
|
||||||
},
|
},
|
||||||
@@ -172,6 +196,18 @@
|
|||||||
"sleep_score": {
|
"sleep_score": {
|
||||||
"name": "Sleep score"
|
"name": "Sleep score"
|
||||||
},
|
},
|
||||||
|
"deep_sleep": {
|
||||||
|
"name": "Deep sleep"
|
||||||
|
},
|
||||||
|
"light_sleep": {
|
||||||
|
"name": "Light sleep"
|
||||||
|
},
|
||||||
|
"rem_sleep": {
|
||||||
|
"name": "REM sleep"
|
||||||
|
},
|
||||||
|
"awake_sleep": {
|
||||||
|
"name": "Awake during sleep"
|
||||||
|
},
|
||||||
"body_battery_most_recent": {
|
"body_battery_most_recent": {
|
||||||
"name": "Body battery (most recent)"
|
"name": "Body battery (most recent)"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ You need a Garmin Connect account with at least one Garmin device that syncs dat
|
|||||||
|
|
||||||
## Sensors
|
## Sensors
|
||||||
|
|
||||||
This integration provides **100+ sensors** covering various health and fitness metrics. Sensors are grouped into the following categories:
|
This integration provides **110+ sensors** covering various health and fitness metrics. Sensors are grouped into the following categories:
|
||||||
|
|
||||||
### Activity & Steps
|
### Activity & Steps
|
||||||
|
|
||||||
- **Total Steps** - Daily step count
|
- **Total Steps** - Daily step count
|
||||||
- **Daily Step Goal** - Your configured step target
|
- **Daily Step Goal** - Your configured step target
|
||||||
|
- **Yesterday Steps/Distance** - Previous day's complete totals
|
||||||
|
- **Weekly Step/Distance Avg** - 7-day averages
|
||||||
- **Total Distance** - Distance walked/run
|
- **Total Distance** - Distance walked/run
|
||||||
- **Floors Ascended/Descended** - Floors climbed
|
- **Floors Ascended/Descended** - Floors climbed
|
||||||
|
|
||||||
@@ -45,6 +47,9 @@ This integration provides **100+ sensors** covering various health and fitness m
|
|||||||
- **Resting Heart Rate** - Daily resting HR
|
- **Resting Heart Rate** - Daily resting HR
|
||||||
- **Min/Max Heart Rate** - Daily HR range
|
- **Min/Max Heart Rate** - Daily HR range
|
||||||
- **Last 7 Days Avg HR** - Weekly average
|
- **Last 7 Days Avg HR** - Weekly average
|
||||||
|
- **HRV Weekly Average** - 7-day HRV average (ms)
|
||||||
|
- **HRV Last Night Average** - Last night's HRV (ms)
|
||||||
|
- **HRV Baseline** - Personal HRV baseline with range
|
||||||
|
|
||||||
### Stress & Recovery
|
### Stress & Recovery
|
||||||
|
|
||||||
@@ -56,6 +61,9 @@ This integration provides **100+ sensors** covering various health and fitness m
|
|||||||
- **Sleep Score** - Overall sleep quality score
|
- **Sleep Score** - Overall sleep quality score
|
||||||
- **Sleep Duration** - Time asleep
|
- **Sleep Duration** - Time asleep
|
||||||
- **Awake Duration** - Time awake during sleep
|
- **Awake Duration** - Time awake during sleep
|
||||||
|
- **Deep Sleep** - Time in deep sleep
|
||||||
|
- **Light Sleep** - Time in light sleep
|
||||||
|
- **REM Sleep** - Time in REM sleep
|
||||||
|
|
||||||
### Body Battery
|
### Body Battery
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user