From e713b01846c015313b2a4892d2d81c4852dfba4c Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Sun, 4 Jan 2026 12:04:55 +0100 Subject: [PATCH] Added morning training readiness, fallback midnight --- .../garmin_connect/coordinator.py | 28 +++++++++++++++---- .../garmin_connect/sensor_descriptions.py | 18 ++++++++++++ .../garmin_connect/translations/en.json | 3 ++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/custom_components/garmin_connect/coordinator.py b/custom_components/garmin_connect/coordinator.py index b308e84..b948b63 100644 --- a/custom_components/garmin_connect/coordinator.py +++ b/custom_components/garmin_connect/coordinator.py @@ -127,14 +127,23 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator): self.api.get_user_summary, today.isoformat() ) - # Midnight fallback: During 0:00-2:00 window, if today's data is empty/None, - # fallback to yesterday's data to avoid gaps due to UTC/GMT timezone differences - if current_hour < 2 and (not summary or summary.get("totalSteps") in (None, 0)): - _LOGGER.debug("Midnight fallback: Today's data empty, fetching yesterday's data") + # 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 + # This works regardless of timezone - no fixed hour window needed + today_data_not_ready = ( + not summary + or summary.get("dailyStepGoal") is None + ) + + if today_data_not_ready: + _LOGGER.debug( + "Today's data not ready (dailyStepGoal=%s), fetching yesterday's data", + summary.get("dailyStepGoal") if summary else None + ) yesterday_summary = await self.hass.async_add_executor_job( self.api.get_user_summary, yesterday_date ) - if yesterday_summary and yesterday_summary.get("totalSteps"): + if yesterday_summary and yesterday_summary.get("dailyStepGoal") is not None: summary = yesterday_summary _LOGGER.debug("Using yesterday's summary data as fallback") @@ -205,6 +214,15 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator): except Exception: summary["trainingReadiness"] = {} + # Fetch morning training readiness (AFTER_WAKEUP_RESET context) + try: + morning_training_readiness = await self.hass.async_add_executor_job( + self.api.get_morning_training_readiness, today.isoformat() + ) + summary["morningTrainingReadiness"] = morning_training_readiness or {} + except Exception: + summary["morningTrainingReadiness"] = {} + # Fetch training status try: training_status = await self.hass.async_add_executor_job( diff --git a/custom_components/garmin_connect/sensor_descriptions.py b/custom_components/garmin_connect/sensor_descriptions.py index c35665a..a550916 100644 --- a/custom_components/garmin_connect/sensor_descriptions.py +++ b/custom_components/garmin_connect/sensor_descriptions.py @@ -886,6 +886,24 @@ ACTIVITY_TRACKING_SENSORS: tuple[GarminConnectSensorEntityDescription, ...] = ( value_fn=lambda data: data.get("trainingStatus", {}).get("trainingStatusPhrase"), attributes_fn=lambda data: data.get("trainingStatus", {}), ), + GarminConnectSensorEntityDescription( + key="morningTrainingReadiness", + translation_key="morning_training_readiness", + icon="mdi:weather-sunset-up", + native_unit_of_measurement=PERCENTAGE, + + value_fn=lambda data: data.get("morningTrainingReadiness", {}).get("score"), + attributes_fn=lambda data: { + "level": data.get("morningTrainingReadiness", {}).get("level"), + "sleep_score": data.get("morningTrainingReadiness", {}).get("sleepScore"), + "recovery_score": data.get("morningTrainingReadiness", {}).get("recoveryScore"), + "hrv_status": data.get("morningTrainingReadiness", {}).get("hrvStatus"), + "acuteLoad": data.get("morningTrainingReadiness", {}).get("acuteLoad"), + "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")}, + }, + ), GarminConnectSensorEntityDescription( key="lactateThresholdHeartRate", translation_key="lactate_threshold_hr", diff --git a/custom_components/garmin_connect/translations/en.json b/custom_components/garmin_connect/translations/en.json index 1a84d95..8481486 100644 --- a/custom_components/garmin_connect/translations/en.json +++ b/custom_components/garmin_connect/translations/en.json @@ -349,6 +349,9 @@ "training_status": { "name": "Training status" }, + "morning_training_readiness": { + "name": "Morning training readiness" + }, "lactate_threshold_hr": { "name": "Lactate threshold heart rate" },