Added goal sensors, fixed polyline card

This commit is contained in:
Ron Klinkien
2026-01-04 15:29:41 +01:00
parent 89ce3e7247
commit 3a7fb30cdf
4 changed files with 241 additions and 161 deletions

View File

@@ -58,9 +58,7 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
"Token not found in config entry. Please reauthenticate." "Token not found in config entry. Please reauthenticate."
) )
await self.hass.async_add_executor_job( await self.hass.async_add_executor_job(self.api.login, self.entry.data[CONF_TOKEN])
self.api.login, self.entry.data[CONF_TOKEN]
)
except ConfigEntryAuthFailed: except ConfigEntryAuthFailed:
raise raise
except GarminConnectAuthenticationError as err: except GarminConnectAuthenticationError as err:
@@ -120,7 +118,12 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
today = datetime.now(ZoneInfo(self.time_zone)).date() today = datetime.now(ZoneInfo(self.time_zone)).date()
current_hour = datetime.now(ZoneInfo(self.time_zone)).hour current_hour = datetime.now(ZoneInfo(self.time_zone)).hour
yesterday_date = (today - timedelta(days=1)).isoformat() yesterday_date = (today - timedelta(days=1)).isoformat()
_LOGGER.debug("Fetching data for date: %s (timezone: %s, hour: %s)", today.isoformat(), self.time_zone, current_hour) _LOGGER.debug(
"Fetching data for date: %s (timezone: %s, hour: %s)",
today.isoformat(),
self.time_zone,
current_hour,
)
try: try:
summary = await self.hass.async_add_executor_job( summary = await self.hass.async_add_executor_job(
@@ -130,15 +133,12 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
# Smart fallback: detect when Garmin servers haven't populated today's data yet # Smart fallback: detect when Garmin servers haven't populated today's data yet
# Key signal: dailyStepGoal is None means the day data structure doesn't exist # Key signal: dailyStepGoal is None means the day data structure doesn't exist
# This works regardless of timezone - no fixed hour window needed # This works regardless of timezone - no fixed hour window needed
today_data_not_ready = ( today_data_not_ready = not summary or summary.get("dailyStepGoal") is None
not summary
or summary.get("dailyStepGoal") is None
)
if today_data_not_ready: if today_data_not_ready:
_LOGGER.debug( _LOGGER.debug(
"Today's data not ready (dailyStepGoal=%s), fetching yesterday's data", "Today's data not ready (dailyStepGoal=%s), fetching yesterday's data",
summary.get("dailyStepGoal") if summary else None summary.get("dailyStepGoal") if summary else None,
) )
yesterday_summary = await self.hass.async_add_executor_job( yesterday_summary = await self.hass.async_add_executor_job(
self.api.get_user_summary, yesterday_date self.api.get_user_summary, yesterday_date
@@ -216,10 +216,10 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
# Fetch workouts (scheduled/planned training sessions) # Fetch workouts (scheduled/planned training sessions)
try: try:
workouts = await self.hass.async_add_executor_job( workouts = await self.hass.async_add_executor_job(self.api.get_workouts, 0, 10)
self.api.get_workouts, 0, 10 summary["workouts"] = (
workouts.get("workouts", []) if isinstance(workouts, dict) else workouts
) )
summary["workouts"] = workouts.get("workouts", []) if isinstance(workouts, dict) else workouts
summary["lastWorkout"] = summary["workouts"][0] if summary["workouts"] else {} summary["lastWorkout"] = summary["workouts"][0] if summary["workouts"] else {}
except Exception: except Exception:
summary["workouts"] = [] summary["workouts"] = []
@@ -264,9 +264,27 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
except Exception: except Exception:
summary["lactateThreshold"] = {} summary["lactateThreshold"] = {}
user_points = sum( # Fetch goals (active, future, past)
badge["badgePoints"] * badge["badgeEarnedNumber"] for badge in badges try:
) active_goals = await self.hass.async_add_executor_job(self.api.get_goals, "active")
summary["activeGoals"] = active_goals or []
except Exception:
summary["activeGoals"] = []
try:
future_goals = await self.hass.async_add_executor_job(self.api.get_goals, "future")
summary["futureGoals"] = future_goals or []
except Exception:
summary["futureGoals"] = []
try:
past_goals = await self.hass.async_add_executor_job(self.api.get_goals, "past")
# Limit to last 10 completed goals
summary["goalsHistory"] = (past_goals or [])[:10]
except Exception:
summary["goalsHistory"] = []
user_points = sum(badge["badgePoints"] * badge["badgeEarnedNumber"] for badge in badges)
summary["userPoints"] = user_points summary["userPoints"] = user_points
user_level = 0 user_level = 0
@@ -278,9 +296,7 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
alarms = await self.hass.async_add_executor_job(self.api.get_device_alarms) alarms = await self.hass.async_add_executor_job(self.api.get_device_alarms)
next_alarms = calculate_next_active_alarms(alarms, self.time_zone) next_alarms = calculate_next_active_alarms(alarms, self.time_zone)
activity_types = await self.hass.async_add_executor_job( activity_types = await self.hass.async_add_executor_job(self.api.get_activity_types)
self.api.get_activity_types
)
sleep_data = await self.hass.async_add_executor_job( sleep_data = await self.hass.async_add_executor_job(
self.api.get_sleep_data, today.isoformat() self.api.get_sleep_data, today.isoformat()
@@ -353,9 +369,7 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
"bpSystolic": latest_bp.get("systolic"), "bpSystolic": latest_bp.get("systolic"),
"bpDiastolic": latest_bp.get("diastolic"), "bpDiastolic": latest_bp.get("diastolic"),
"bpPulse": latest_bp.get("pulse"), "bpPulse": latest_bp.get("pulse"),
"bpMeasurementTime": latest_bp.get( "bpMeasurementTime": latest_bp.get("measurementTimestampLocal"),
"measurementTimestampLocal"
),
} }
except Exception as err: except Exception as err:
_LOGGER.debug("Blood pressure data not available: %s", err) _LOGGER.debug("Blood pressure data not available: %s", err)
@@ -380,9 +394,7 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
try: try:
if gear: if gear:
tasks: list[Awaitable] = [ tasks: list[Awaitable] = [
self.hass.async_add_executor_job( self.hass.async_add_executor_job(self.api.get_gear_stats, gear_item[Gear.UUID])
self.api.get_gear_stats, gear_item[Gear.UUID]
)
for gear_item in gear for gear_item in gear
] ]
gear_stats = await asyncio.gather(*tasks) gear_stats = await asyncio.gather(*tasks)
@@ -506,9 +518,7 @@ def calculate_next_active_alarms(alarms: Any, time_zone: str) -> list[str] | Non
datetime.min.time(), datetime.min.time(),
tzinfo=ZoneInfo(time_zone), tzinfo=ZoneInfo(time_zone),
) )
alarm = start_of_week + timedelta( alarm = start_of_week + timedelta(days=DAY_TO_NUMBER[day] - 1, minutes=alarm_time)
days=DAY_TO_NUMBER[day] - 1, minutes=alarm_time
)
if alarm < now: if alarm < now:
alarm += timedelta(days=7) alarm += timedelta(days=7)

View File

@@ -82,7 +82,6 @@ class GarminConnectSensorEntityDescription(SensorEntityDescription):
"""If True, preserve last known value when API returns None (for weight, BMI, etc).""" """If True, preserve last known value when API returns None (for weight, BMI, etc)."""
# Activity & Steps Sensors # Activity & Steps Sensors
ACTIVITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = ( ACTIVITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
@@ -196,7 +195,6 @@ CALORIES_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement="kcal", native_unit_of_measurement="kcal",
icon="mdi:food", icon="mdi:food",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="remainingKilocalories", key="remainingKilocalories",
@@ -204,7 +202,6 @@ CALORIES_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement="kcal", native_unit_of_measurement="kcal",
icon="mdi:food", icon="mdi:food",
), ),
) )
@@ -237,16 +234,19 @@ HEART_RATE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="bpm", native_unit_of_measurement="bpm",
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="hrvStatus", key="hrvStatus",
translation_key="hrv_status", translation_key="hrv_status",
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
value_fn=lambda data: data.get("hrvStatus", {}).get( value_fn=lambda data: data.get("hrvStatus", {}).get("status", "").capitalize()
"status", "").capitalize() if data.get("hrvStatus") else None, if data.get("hrvStatus")
attributes_fn=lambda data: {k: v for k, v in data.get( else None,
"hrvStatus", {}).items() if k != "status"} if data.get("hrvStatus") else {}, attributes_fn=lambda data: {
k: v for k, v in data.get("hrvStatus", {}).items() if k != "status"
}
if data.get("hrvStatus")
else {},
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="hrvWeeklyAvg", key="hrvWeeklyAvg",
@@ -278,8 +278,9 @@ HEART_RATE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="ms", native_unit_of_measurement="ms",
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
value_fn=lambda data: data.get("hrvStatus", {}).get("baseline", {}).get( value_fn=lambda data: data.get("hrvStatus", {}).get("baseline", {}).get("lowUpper")
"lowUpper") if data.get("hrvStatus", {}).get("baseline") else None, if data.get("hrvStatus", {}).get("baseline")
else None,
attributes_fn=lambda data: data.get("hrvStatus", {}).get("baseline", {}), attributes_fn=lambda data: data.get("hrvStatus", {}).get("baseline", {}),
), ),
) )
@@ -304,9 +305,9 @@ STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
key="stressQualifier", key="stressQualifier",
translation_key="stress_qualifier", translation_key="stress_qualifier",
icon="mdi:emoticon", icon="mdi:emoticon",
value_fn=lambda data: data.get("stressQualifier", "").capitalize( value_fn=lambda data: data.get("stressQualifier", "").capitalize()
) if data.get("stressQualifier") else None, if data.get("stressQualifier")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="totalStressDuration", key="totalStressDuration",
@@ -315,8 +316,9 @@ STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:timer", icon="mdi:timer",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("totalStressDuration", 0) / 60, 2)
"totalStressDuration", 0) / 60, 2) if data.get("totalStressDuration") else None, if data.get("totalStressDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="restStressDuration", key="restStressDuration",
@@ -325,8 +327,9 @@ STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:timer-pause", icon="mdi:timer-pause",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("restStressDuration", 0) / 60, 2)
"restStressDuration", 0) / 60, 2) if data.get("restStressDuration") else None, if data.get("restStressDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="activityStressDuration", key="activityStressDuration",
@@ -335,8 +338,9 @@ STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:timer-play", icon="mdi:timer-play",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("activityStressDuration", 0) / 60, 2)
"activityStressDuration", 0) / 60, 2) if data.get("activityStressDuration") else None, if data.get("activityStressDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="lowStressDuration", key="lowStressDuration",
@@ -345,8 +349,9 @@ STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:timer-check", icon="mdi:timer-check",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("lowStressDuration", 0) / 60, 2)
"lowStressDuration", 0) / 60, 2) if data.get("lowStressDuration") else None, if data.get("lowStressDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="mediumStressDuration", key="mediumStressDuration",
@@ -355,8 +360,9 @@ STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:timer-alert", icon="mdi:timer-alert",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("mediumStressDuration", 0) / 60, 2)
"mediumStressDuration", 0) / 60, 2) if data.get("mediumStressDuration") else None, if data.get("mediumStressDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="highStressDuration", key="highStressDuration",
@@ -365,8 +371,9 @@ STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:timer-remove", icon="mdi:timer-remove",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("highStressDuration", 0) / 60, 2)
"highStressDuration", 0) / 60, 2) if data.get("highStressDuration") else None, if data.get("highStressDuration")
else None,
), ),
) )
@@ -379,8 +386,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep", icon="mdi:sleep",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("sleepingSeconds", 0) / 60, 2)
"sleepingSeconds", 0) / 60, 2) if data.get("sleepingSeconds") else None, if data.get("sleepingSeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="sleepTimeSeconds", key="sleepTimeSeconds",
@@ -389,8 +397,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep", icon="mdi:sleep",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("sleepTimeSeconds", 0) / 60, 2)
"sleepTimeSeconds", 0) / 60, 2) if data.get("sleepTimeSeconds") else None, if data.get("sleepTimeSeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="measurableAsleepDuration", key="measurableAsleepDuration",
@@ -399,8 +408,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep", icon="mdi:sleep",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("measurableAsleepDuration", 0) / 60, 2)
"measurableAsleepDuration", 0) / 60, 2) if data.get("measurableAsleepDuration") else None, if data.get("measurableAsleepDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="measurableAwakeDuration", key="measurableAwakeDuration",
@@ -409,8 +419,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep-off", icon="mdi:sleep-off",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("measurableAwakeDuration", 0) / 60, 2)
"measurableAwakeDuration", 0) / 60, 2) if data.get("measurableAwakeDuration") else None, if data.get("measurableAwakeDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="sleepScore", key="sleepScore",
@@ -425,8 +436,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep", icon="mdi:sleep",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("deepSleepSeconds", 0) / 60, 2)
"deepSleepSeconds", 0) / 60, 2) if data.get("deepSleepSeconds") else None, if data.get("deepSleepSeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="lightSleepSeconds", key="lightSleepSeconds",
@@ -435,8 +447,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep", icon="mdi:sleep",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("lightSleepSeconds", 0) / 60, 2)
"lightSleepSeconds", 0) / 60, 2) if data.get("lightSleepSeconds") else None, if data.get("lightSleepSeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="remSleepSeconds", key="remSleepSeconds",
@@ -445,8 +458,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep", icon="mdi:sleep",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("remSleepSeconds", 0) / 60, 2)
"remSleepSeconds", 0) / 60, 2) if data.get("remSleepSeconds") else None, if data.get("remSleepSeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="awakeSleepSeconds", key="awakeSleepSeconds",
@@ -455,8 +469,9 @@ SLEEP_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:sleep-off", icon="mdi:sleep-off",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("awakeSleepSeconds", 0) / 60, 2)
"awakeSleepSeconds", 0) / 60, 2) if data.get("awakeSleepSeconds") else None, if data.get("awakeSleepSeconds")
else None,
), ),
) )
@@ -508,8 +523,9 @@ BODY_COMPOSITION_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfMass.KILOGRAMS, native_unit_of_measurement=UnitOfMass.KILOGRAMS,
icon="mdi:weight-kilogram", icon="mdi:weight-kilogram",
value_fn=lambda data: round( value_fn=lambda data: round(data.get("weight", 0) / 1000, 2)
data.get("weight", 0) / 1000, 2) if data.get("weight") else None, if data.get("weight")
else None,
preserve_value=True, preserve_value=True,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
@@ -543,8 +559,9 @@ BODY_COMPOSITION_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfMass.KILOGRAMS, native_unit_of_measurement=UnitOfMass.KILOGRAMS,
icon="mdi:bone", icon="mdi:bone",
value_fn=lambda data: round( value_fn=lambda data: round(data.get("boneMass", 0) / 1000, 2)
data.get("boneMass", 0) / 1000, 2) if data.get("boneMass") else None, if data.get("boneMass")
else None,
preserve_value=True, preserve_value=True,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
@@ -554,8 +571,9 @@ BODY_COMPOSITION_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfMass.KILOGRAMS, native_unit_of_measurement=UnitOfMass.KILOGRAMS,
icon="mdi:dumbbell", icon="mdi:dumbbell",
value_fn=lambda data: round( value_fn=lambda data: round(data.get("muscleMass", 0) / 1000, 2)
data.get("muscleMass", 0) / 1000, 2) if data.get("muscleMass") else None, if data.get("muscleMass")
else None,
preserve_value=True, preserve_value=True,
), ),
) )
@@ -608,8 +626,9 @@ INTENSITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:fire", icon="mdi:fire",
value_fn=lambda data: round( value_fn=lambda data: round(data.get("activeSeconds", 0) / 60, 2)
data.get("activeSeconds", 0) / 60, 2) if data.get("activeSeconds") else None, if data.get("activeSeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="highlyActiveSeconds", key="highlyActiveSeconds",
@@ -618,9 +637,9 @@ INTENSITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:fire", icon="mdi:fire",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("highlyActiveSeconds", 0) / 60, 2)
"highlyActiveSeconds", 0) / 60, 2) if data.get("highlyActiveSeconds") else None, if data.get("highlyActiveSeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="sedentarySeconds", key="sedentarySeconds",
@@ -629,8 +648,9 @@ INTENSITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:seat", icon="mdi:seat",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("sedentarySeconds", 0) / 60, 2)
"sedentarySeconds", 0) / 60, 2) if data.get("sedentarySeconds") else None, if data.get("sedentarySeconds")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="moderateIntensityMinutes", key="moderateIntensityMinutes",
@@ -639,7 +659,6 @@ INTENSITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="vigorousIntensityMinutes", key="vigorousIntensityMinutes",
@@ -648,7 +667,6 @@ INTENSITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:run-fast", icon="mdi:run-fast",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="intensityMinutesGoal", key="intensityMinutesGoal",
@@ -657,7 +675,6 @@ INTENSITY_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:target", icon="mdi:target",
), ),
) )
@@ -689,7 +706,6 @@ HEALTH_MONITORING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="latest_spo2_time", translation_key="latest_spo2_time",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
icon="mdi:clock", icon="mdi:clock",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="highestRespirationValue", key="highestRespirationValue",
@@ -697,7 +713,6 @@ HEALTH_MONITORING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="brpm", native_unit_of_measurement="brpm",
icon="mdi:progress-clock", icon="mdi:progress-clock",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="lowestRespirationValue", key="lowestRespirationValue",
@@ -705,7 +720,6 @@ HEALTH_MONITORING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="brpm", native_unit_of_measurement="brpm",
icon="mdi:progress-clock", icon="mdi:progress-clock",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="latestRespirationValue", key="latestRespirationValue",
@@ -713,14 +727,12 @@ HEALTH_MONITORING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="brpm", native_unit_of_measurement="brpm",
icon="mdi:progress-clock", icon="mdi:progress-clock",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="latestRespirationTimeGMT", key="latestRespirationTimeGMT",
translation_key="latest_respiration_time", translation_key="latest_respiration_time",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
icon="mdi:clock", icon="mdi:clock",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="averageMonitoringEnvironmentAltitude", key="averageMonitoringEnvironmentAltitude",
@@ -728,7 +740,6 @@ HEALTH_MONITORING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:image-filter-hdr", icon="mdi:image-filter-hdr",
), ),
) )
@@ -775,7 +786,6 @@ FITNESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="endurance_score", translation_key="endurance_score",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
icon="mdi:run", icon="mdi:run",
value_fn=lambda data: data.get("enduranceScore", {}).get("overallScore"), value_fn=lambda data: data.get("enduranceScore", {}).get("overallScore"),
attributes_fn=lambda data: { attributes_fn=lambda data: {
**{k: v for k, v in data.get("enduranceScore", {}).items() if k != "overallScore"}, **{k: v for k, v in data.get("enduranceScore", {}).items() if k != "overallScore"},
@@ -817,7 +827,6 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
icon="mdi:alarm", icon="mdi:alarm",
value_fn=lambda data: data.get("nextAlarm", [None])[0] if data.get("nextAlarm") else None, value_fn=lambda data: data.get("nextAlarm", [None])[0] if data.get("nextAlarm") else None,
attributes_fn=lambda data: { attributes_fn=lambda data: {
"next_alarms": data.get("nextAlarm"), "next_alarms": data.get("nextAlarm"),
}, },
), ),
@@ -825,7 +834,6 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
key="lastActivity", key="lastActivity",
translation_key="last_activity", translation_key="last_activity",
icon="mdi:walk", icon="mdi:walk",
value_fn=lambda data: data.get("lastActivity", {}).get("activityName"), value_fn=lambda data: data.get("lastActivity", {}).get("activityName"),
attributes_fn=lambda data: _trim_activity(data.get("lastActivity", {})), attributes_fn=lambda data: _trim_activity(data.get("lastActivity", {})),
), ),
@@ -834,11 +842,11 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="last_activities", translation_key="last_activities",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
icon="mdi:numeric", icon="mdi:numeric",
value_fn=lambda data: len(data.get("lastActivities", [])), value_fn=lambda data: len(data.get("lastActivities", [])),
attributes_fn=lambda data: { attributes_fn=lambda data: {
"last_activities": [ "last_activities": [
_trim_activity(a) for a in sorted( _trim_activity(a)
for a in sorted(
data.get("lastActivities", []), data.get("lastActivities", []),
key=lambda x: x.get("activityId", 0), key=lambda x: x.get("activityId", 0),
)[-10:] )[-10:]
@@ -849,7 +857,6 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
key="lastWorkout", key="lastWorkout",
translation_key="last_workout", translation_key="last_workout",
icon="mdi:dumbbell", icon="mdi:dumbbell",
value_fn=lambda data: data.get("lastWorkout", {}).get("workoutName"), value_fn=lambda data: data.get("lastWorkout", {}).get("workoutName"),
attributes_fn=lambda data: data.get("lastWorkout", {}), attributes_fn=lambda data: data.get("lastWorkout", {}),
), ),
@@ -858,7 +865,6 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="last_workouts", translation_key="last_workouts",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
icon="mdi:dumbbell", icon="mdi:dumbbell",
value_fn=lambda data: len(data.get("workouts", [])), value_fn=lambda data: len(data.get("workouts", [])),
attributes_fn=lambda data: { attributes_fn=lambda data: {
"last_workouts": data.get("workouts", [])[-10:], "last_workouts": data.get("workouts", [])[-10:],
@@ -869,23 +875,25 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="training_readiness", translation_key="training_readiness",
icon="mdi:run-fast", icon="mdi:run-fast",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: data.get("trainingReadiness", {}).get("score") value_fn=lambda data: data.get("trainingReadiness", {}).get("score")
if isinstance(data.get("trainingReadiness"), dict) if isinstance(data.get("trainingReadiness"), dict)
else (data.get("trainingReadiness", [{}])[0].get("score") else (
if isinstance(data.get("trainingReadiness"), list) and data.get("trainingReadiness") data.get("trainingReadiness", [{}])[0].get("score")
else None), if isinstance(data.get("trainingReadiness"), list) and data.get("trainingReadiness")
else None
),
attributes_fn=lambda data: data.get("trainingReadiness", {}) attributes_fn=lambda data: data.get("trainingReadiness", {})
if isinstance(data.get("trainingReadiness"), dict) if isinstance(data.get("trainingReadiness"), dict)
else (data.get("trainingReadiness", [{}])[0] else (
if isinstance(data.get("trainingReadiness"), list) and data.get("trainingReadiness") data.get("trainingReadiness", [{}])[0]
else {}), if isinstance(data.get("trainingReadiness"), list) and data.get("trainingReadiness")
else {}
),
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="trainingStatus", key="trainingStatus",
translation_key="training_status", translation_key="training_status",
icon="mdi:chart-line", icon="mdi:chart-line",
value_fn=lambda data: data.get("trainingStatus", {}).get("trainingStatusPhrase"), value_fn=lambda data: data.get("trainingStatus", {}).get("trainingStatusPhrase"),
attributes_fn=lambda data: data.get("trainingStatus", {}), attributes_fn=lambda data: data.get("trainingStatus", {}),
), ),
@@ -894,7 +902,6 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="morning_training_readiness", translation_key="morning_training_readiness",
icon="mdi:weather-sunset-up", icon="mdi:weather-sunset-up",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
value_fn=lambda data: data.get("morningTrainingReadiness", {}).get("score"), value_fn=lambda data: data.get("morningTrainingReadiness", {}).get("score"),
attributes_fn=lambda data: { attributes_fn=lambda data: {
"level": data.get("morningTrainingReadiness", {}).get("level"), "level": data.get("morningTrainingReadiness", {}).get("level"),
@@ -903,8 +910,20 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
"hrv_status": data.get("morningTrainingReadiness", {}).get("hrvStatus"), "hrv_status": data.get("morningTrainingReadiness", {}).get("hrvStatus"),
"acuteLoad": data.get("morningTrainingReadiness", {}).get("acuteLoad"), "acuteLoad": data.get("morningTrainingReadiness", {}).get("acuteLoad"),
"input_context": data.get("morningTrainingReadiness", {}).get("inputContext"), "input_context": data.get("morningTrainingReadiness", {}).get("inputContext"),
**{k: v for k, v in data.get("morningTrainingReadiness", {}).items() **{
if k not in ("score", "level", "sleepScore", "recoveryScore", "hrvStatus", "acuteLoad", "inputContext")}, k: v
for k, v in data.get("morningTrainingReadiness", {}).items()
if k
not in (
"score",
"level",
"sleepScore",
"recoveryScore",
"hrvStatus",
"acuteLoad",
"inputContext",
)
},
}, },
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
@@ -912,10 +931,9 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="lactate_threshold_hr", translation_key="lactate_threshold_hr",
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
native_unit_of_measurement="bpm", native_unit_of_measurement="bpm",
value_fn=lambda data: data.get("lactateThreshold", {})
value_fn=lambda data: data.get("lactateThreshold", {}).get( .get("speed_and_heart_rate", {})
"speed_and_heart_rate", {} .get("heartRate"),
).get("heartRate"),
attributes_fn=lambda data: data.get("lactateThreshold", {}), attributes_fn=lambda data: data.get("lactateThreshold", {}),
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
@@ -923,10 +941,9 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="lactate_threshold_speed", translation_key="lactate_threshold_speed",
icon="mdi:speedometer", icon="mdi:speedometer",
native_unit_of_measurement="m/s", native_unit_of_measurement="m/s",
value_fn=lambda data: data.get("lactateThreshold", {})
value_fn=lambda data: data.get("lactateThreshold", {}).get( .get("speed_and_heart_rate", {})
"speed_and_heart_rate", {} .get("speed"),
).get("speed"),
attributes_fn=lambda data: data.get("lactateThreshold", {}), attributes_fn=lambda data: data.get("lactateThreshold", {}),
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
@@ -934,10 +951,8 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="badges", translation_key="badges",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
icon="mdi:medal", icon="mdi:medal",
value_fn=lambda data: len(data.get("badges", [])), value_fn=lambda data: len(data.get("badges", [])),
attributes_fn=lambda data: { attributes_fn=lambda data: {
"badges": sorted( "badges": sorted(
data.get("badges", []), data.get("badges", []),
key=lambda x: x.get("badgeEarnedDate", ""), key=lambda x: x.get("badgeEarnedDate", ""),
@@ -949,14 +964,82 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="user_points", translation_key="user_points",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
icon="mdi:counter", icon="mdi:counter",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="userLevel", key="userLevel",
translation_key="user_level", translation_key="user_level",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
icon="mdi:star-four-points-circle", icon="mdi:star-four-points-circle",
),
GarminConnectSensorEntityDescription(
key="activeGoals",
translation_key="active_goals",
state_class=SensorStateClass.TOTAL,
icon="mdi:flag-checkered",
value_fn=lambda data: len(data.get("activeGoals", [])),
attributes_fn=lambda data: {
"goals": [
{
"goalType": g.get("goalType"),
"goalDescription": g.get("goalDescription"),
"targetValue": g.get("targetValue"),
"currentValue": g.get("currentValue"),
"progressPercent": round(
(g.get("currentValue", 0) / g.get("targetValue", 1)) * 100, 1
)
if g.get("targetValue")
else 0,
"startDate": g.get("startDate"),
"endDate": g.get("endDate"),
"activityType": g.get("activityType"),
}
for g in data.get("activeGoals", [])
],
},
),
GarminConnectSensorEntityDescription(
key="futureGoals",
translation_key="future_goals",
state_class=SensorStateClass.TOTAL,
icon="mdi:calendar-clock",
value_fn=lambda data: len(data.get("futureGoals", [])),
attributes_fn=lambda data: {
"goals": [
{
"goalType": g.get("goalType"),
"goalDescription": g.get("goalDescription"),
"targetValue": g.get("targetValue"),
"startDate": g.get("startDate"),
"endDate": g.get("endDate"),
"activityType": g.get("activityType"),
}
for g in data.get("futureGoals", [])
],
},
),
GarminConnectSensorEntityDescription(
key="goalsHistory",
translation_key="goals_history",
state_class=SensorStateClass.TOTAL,
icon="mdi:history",
value_fn=lambda data: len(data.get("goalsHistory", [])),
attributes_fn=lambda data: {
"goals": [
{
"goalType": g.get("goalType"),
"goalDescription": g.get("goalDescription"),
"targetValue": g.get("targetValue"),
"currentValue": g.get("currentValue"),
"achieved": g.get("currentValue", 0) >= g.get("targetValue", 1)
if g.get("targetValue")
else False,
"startDate": g.get("startDate"),
"endDate": g.get("endDate"),
"activityType": g.get("activityType"),
}
for g in data.get("goalsHistory", [])
],
},
), ),
) )
@@ -968,7 +1051,6 @@ ADDITIONAL_HEART_RATE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...]
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="bpm", native_unit_of_measurement="bpm",
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="maxAvgHeartRate", key="maxAvgHeartRate",
@@ -976,14 +1058,12 @@ ADDITIONAL_HEART_RATE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...]
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="bpm", native_unit_of_measurement="bpm",
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="abnormalHeartRateAlertsCount", key="abnormalHeartRateAlertsCount",
translation_key="abnormal_hr_alerts", translation_key="abnormal_hr_alerts",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
icon="mdi:heart-pulse", icon="mdi:heart-pulse",
), ),
) )
@@ -995,7 +1075,6 @@ STRESS_PERCENTAGE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="restStressPercentage", key="restStressPercentage",
@@ -1003,7 +1082,6 @@ STRESS_PERCENTAGE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="activityStressPercentage", key="activityStressPercentage",
@@ -1011,7 +1089,6 @@ STRESS_PERCENTAGE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="uncategorizedStressPercentage", key="uncategorizedStressPercentage",
@@ -1019,7 +1096,6 @@ STRESS_PERCENTAGE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="lowStressPercentage", key="lowStressPercentage",
@@ -1027,7 +1103,6 @@ STRESS_PERCENTAGE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="mediumStressPercentage", key="mediumStressPercentage",
@@ -1035,7 +1110,6 @@ STRESS_PERCENTAGE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="highStressPercentage", key="highStressPercentage",
@@ -1043,7 +1117,6 @@ STRESS_PERCENTAGE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
icon="mdi:flash-alert", icon="mdi:flash-alert",
), ),
) )
@@ -1056,8 +1129,9 @@ ADDITIONAL_STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:flash-alert", icon="mdi:flash-alert",
value_fn=lambda data: round(data.get( value_fn=lambda data: round(data.get("uncategorizedStressDuration", 0) / 60, 2)
"uncategorizedStressDuration", 0) / 60, 2) if data.get("uncategorizedStressDuration") else None, if data.get("uncategorizedStressDuration")
else None,
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="stressDuration", key="stressDuration",
@@ -1066,9 +1140,9 @@ ADDITIONAL_STRESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfTime.MINUTES, native_unit_of_measurement=UnitOfTime.MINUTES,
icon="mdi:flash-alert", icon="mdi:flash-alert",
value_fn=lambda data: round( value_fn=lambda data: round(data.get("stressDuration", 0) / 60, 2)
data.get("stressDuration", 0) / 60, 2) if data.get("stressDuration") else None, if data.get("stressDuration")
else None,
), ),
) )
@@ -1081,7 +1155,6 @@ ADDITIONAL_DISTANCE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] =
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfLength.METERS, native_unit_of_measurement=UnitOfLength.METERS,
icon="mdi:stairs-up", icon="mdi:stairs-up",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="floorsDescendedInMeters", key="floorsDescendedInMeters",
@@ -1090,7 +1163,6 @@ ADDITIONAL_DISTANCE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] =
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfLength.METERS, native_unit_of_measurement=UnitOfLength.METERS,
icon="mdi:stairs-down", icon="mdi:stairs-down",
), ),
) )
@@ -1101,21 +1173,18 @@ WELLNESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
translation_key="wellness_start_time", translation_key="wellness_start_time",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
icon="mdi:clock", icon="mdi:clock",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="wellnessEndTimeLocal", key="wellnessEndTimeLocal",
translation_key="wellness_end_time", translation_key="wellness_end_time",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
icon="mdi:clock", icon="mdi:clock",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="wellnessDescription", key="wellnessDescription",
translation_key="wellness_description", translation_key="wellness_description",
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
icon="mdi:text", icon="mdi:text",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="wellnessDistanceMeters", key="wellnessDistanceMeters",
@@ -1124,7 +1193,6 @@ WELLNESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement=UnitOfLength.METERS, native_unit_of_measurement=UnitOfLength.METERS,
icon="mdi:walk", icon="mdi:walk",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="wellnessActiveKilocalories", key="wellnessActiveKilocalories",
@@ -1132,7 +1200,6 @@ WELLNESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement="kcal", native_unit_of_measurement="kcal",
icon="mdi:fire", icon="mdi:fire",
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="wellnessKilocalories", key="wellnessKilocalories",
@@ -1140,7 +1207,6 @@ WELLNESS_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
native_unit_of_measurement="kcal", native_unit_of_measurement="kcal",
icon="mdi:fire", icon="mdi:fire",
), ),
) )
@@ -1153,9 +1219,9 @@ MENSTRUAL_CYCLE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
icon="mdi:calendar-heart", icon="mdi:calendar-heart",
value_fn=lambda data: data.get("menstrualData", {}).get("currentPhase"), value_fn=lambda data: data.get("menstrualData", {}).get("currentPhase"),
attributes_fn=lambda data: { attributes_fn=lambda data: {
**{
**{k: v for k, v in data.get("menstrualData", {}).items() k: v for k, v in data.get("menstrualData", {}).items() if k not in ("currentPhase",)
if k not in ("currentPhase",)}, },
}, },
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
@@ -1164,9 +1230,7 @@ MENSTRUAL_CYCLE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
icon="mdi:calendar-today", icon="mdi:calendar-today",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.get("menstrualData", {}).get("dayOfCycle"), value_fn=lambda data: data.get("menstrualData", {}).get("dayOfCycle"),
attributes_fn=lambda data: { attributes_fn=lambda data: {},
},
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="menstrualPeriodDay", key="menstrualPeriodDay",
@@ -1174,9 +1238,7 @@ MENSTRUAL_CYCLE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
icon="mdi:water", icon="mdi:water",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.get("menstrualData", {}).get("dayOfPeriod"), value_fn=lambda data: data.get("menstrualData", {}).get("dayOfPeriod"),
attributes_fn=lambda data: { attributes_fn=lambda data: {},
},
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="menstrualCycleLength", key="menstrualCycleLength",
@@ -1185,9 +1247,7 @@ MENSTRUAL_CYCLE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.DAYS, native_unit_of_measurement=UnitOfTime.DAYS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.get("menstrualData", {}).get("cycleLength"), value_fn=lambda data: data.get("menstrualData", {}).get("cycleLength"),
attributes_fn=lambda data: { attributes_fn=lambda data: {},
},
), ),
GarminConnectSensorEntityDescription( GarminConnectSensorEntityDescription(
key="menstrualPeriodLength", key="menstrualPeriodLength",
@@ -1196,9 +1256,7 @@ MENSTRUAL_CYCLE_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTime.DAYS, native_unit_of_measurement=UnitOfTime.DAYS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.get("menstrualData", {}).get("periodLength"), value_fn=lambda data: data.get("menstrualData", {}).get("periodLength"),
attributes_fn=lambda data: { attributes_fn=lambda data: {},
},
), ),
) )
@@ -1273,6 +1331,3 @@ ALL_SENSOR_DESCRIPTIONS: tuple[GarminConnectSensorEntityDescription, ...] = (
*BLOOD_PRESSURE_SENSORS, *BLOOD_PRESSURE_SENSORS,
*DIAGNOSTIC_SENSORS, *DIAGNOSTIC_SENSORS,
) )

View File

@@ -367,6 +367,15 @@
"user_level": { "user_level": {
"name": "User level" "name": "User level"
}, },
"active_goals": {
"name": "Active goals"
},
"future_goals": {
"name": "Future goals"
},
"goals_history": {
"name": "Goals history"
},
"wellness_start_time": { "wellness_start_time": {
"name": "Wellness start time" "name": "Wellness start time"
}, },

View File

@@ -105,6 +105,12 @@ class GarminPolylineCard extends HTMLElement {
const mapContainer = this.shadowRoot.getElementById('map'); const mapContainer = this.shadowRoot.getElementById('map');
if (!mapContainer || !window.L) return; if (!mapContainer || !window.L) return;
// If map already exists, remove it first
if (this._map) {
this._map.remove();
this._map = null;
}
// Create map // Create map
this._map = L.map(mapContainer, { this._map = L.map(mapContainer, {
zoomControl: true, zoomControl: true,