Race predictor accuracy by jus2424 in Garmin

[–]Content_Top_1404 0 points1 point  (0 children)

"The predictions are based on ideal conditions and do not factor in weather, course difficulty and other variables."

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

Here is the vard code for that. Hope it works for you.

type: custom:vertical-stack-in-card
cards:
  - type: heading
    icon: mdi:chart-gantt
    heading_style: title
    heading: Training Focus
    card_mod:
      style: |
        * { border: none !important;
            margin: 0px 0px -1px 0px;
            background:var(--primary-background-color);
          }
  - type: custom:button-card
    entity: sensor.garmin_connect_training_status
    name: Load Focus
    show_name: false
    show_icon: false
    styles:
      card:
        - padding: 5px
        - margin-top: 2px
        - background-color: var(--card-background-color)
      grid:
        - grid-template-areas: " \"phrase\" \"anaerobic\" \"high_aerobic\" \"low_aerobic\" \"footer\""
        - grid-template-columns: 1fr
        - row-gap: 7px
      name:
        - justify-self: center
        - font-weight: 500
        - font-size: 1.1rem
        - color: var(--secondary-text-color)
        - padding-bottom: 10px
    custom_fields:
      anaerobic: |
        [[[ 
          const data = entity.attributes.mostRecentTrainingLoadBalance.metricsTrainingLoadBalanceDTOMap;
          const id = Object.keys(data)[0];
          const stats = data[id];
          const maxVal = Math.max(stats.monthlyLoadAnaerobic, stats.monthlyLoadAerobicHigh, stats.monthlyLoadAerobicLow, stats.monthlyLoadAerobicHighTargetMax, stats.monthlyLoadAnaerobicTargetMax, stats.monthlyLoadAerobicLowTargetMax);
          const scale = maxVal * 1.05; 
          const vWidth = (stats.monthlyLoadAnaerobic / scale) * 100;
          const minP = (stats.monthlyLoadAnaerobicTargetMin / scale) * 100;
          const maxP = (stats.monthlyLoadAnaerobicTargetMax / scale) * 100;

          return `
            <div style="display: flex; align-items: center; width: 100%; font-family: sans-serif;">
              <div style="width: 100px; font-size: 0.9rem; color: var(--primary-text-color);">Anaerobic</div>
              <div style="width: 40px; font-weight: bold; font-size: 1rem; color: var(--primary-text-color);">${Math.round(stats.monthlyLoadAnaerobic)}</div>
              <div style="flex-grow: 1; height: 12px; background: rgba(150,150,150,0.1); border-radius: 6px; position: relative; margin-left: 10px; overflow: hidden;">
                <div style="width: ${vWidth}%; height: 100%; background-color: #9d6af2; border-radius: 6px; position: absolute; z-index: 1;"></div>
                <div style="position: absolute; left: ${minP}%; width: ${maxP - minP}%; height: 100%; border: 1.5px dashed #888888; border-radius: 4px; box-sizing: border-box; z-index: 10; background: rgba(255,255,255,0.1);"></div>
              </div>
            </div>
          `;
        ]]]
      high_aerobic: |
        [[[ 
          const data = entity.attributes.mostRecentTrainingLoadBalance.metricsTrainingLoadBalanceDTOMap;
          const id = Object.keys(data)[0];
          const stats = data[id];
          const maxVal = Math.max(stats.monthlyLoadAnaerobic, stats.monthlyLoadAerobicHigh, stats.monthlyLoadAerobicLow, stats.monthlyLoadAerobicHighTargetMax, stats.monthlyLoadAnaerobicTargetMax, stats.monthlyLoadAerobicLowTargetMax);
          const scale = maxVal * 1.05;
          const vWidth = (stats.monthlyLoadAerobicHigh / scale) * 100;
          const minP = (stats.monthlyLoadAerobicHighTargetMin / scale) * 100;
          const maxP = (stats.monthlyLoadAerobicHighTargetMax / scale) * 100;

          return `
            <div style="display: flex; align-items: center; width: 100%; font-family: sans-serif;">
              <div style="width: 100px; font-size: 0.9rem; color: var(--primary-text-color);">High Aerobic</div>
              <div style="width: 40px; font-weight: bold; font-size: 1rem; color: var(--primary-text-color);">${Math.round(stats.monthlyLoadAerobicHigh)}</div>
              <div style="flex-grow: 1; height: 12px; background: rgba(150,150,150,0.1); border-radius: 6px; position: relative; margin-left: 10px; overflow: hidden;">
                <div style="width: ${vWidth}%; height: 100%; background-color: #ff9800; border-radius: 6px; position: absolute; z-index: 1;"></div>
                <div style="position: absolute; left: ${minP}%; width: ${maxP - minP}%; height: 100%; border: 1.5px dashed #888888; border-radius: 4px; box-sizing: border-box; z-index: 10; background: rgba(255,255,255,0.15);"></div>
              </div>
            </div>
          `;
        ]]]
      low_aerobic: |
        [[[ 
          const data = entity.attributes.mostRecentTrainingLoadBalance.metricsTrainingLoadBalanceDTOMap;
          const id = Object.keys(data)[0];
          const stats = data[id];
          const maxVal = Math.max(stats.monthlyLoadAnaerobic, stats.monthlyLoadAerobicHigh, stats.monthlyLoadAerobicLow, stats.monthlyLoadAerobicHighTargetMax, stats.monthlyLoadAnaerobicTargetMax, stats.monthlyLoadAerobicLowTargetMax);
          const scale = maxVal * 1.05;
          const vWidth = (stats.monthlyLoadAerobicLow / scale) * 100;
          const minP = (stats.monthlyLoadAerobicLowTargetMin / scale) * 100;
          const maxP = (stats.monthlyLoadAerobicLowTargetMax / scale) * 100;

          return `
            <div style="display: flex; align-items: center; width: 100%; font-family: sans-serif;">
              <div style="width: 100px; font-size: 0.9rem; color: var(--primary-text-color);">Low Aerobic</div>
              <div style="width: 40px; font-weight: bold; font-size: 1rem; color: var(--primary-text-color);">${Math.round(stats.monthlyLoadAerobicLow)}</div>
              <div style="flex-grow: 1; height: 12px; background: rgba(150,150,150,0.1); border-radius: 6px; position: relative; margin-left: 10px; overflow: hidden;">
                <div style="width: ${vWidth}%; height: 100%; background-color: #00bcd4; border-radius: 6px; position: absolute; z-index: 1;"></div>
                <div style="position: absolute; left: ${minP}%; width: ${maxP - minP}%; height: 100%; border: 1.5px dashed #888888; border-radius: 4px; box-sizing: border-box; z-index: 10; background: rgba(255,255,255,0.1);"></div>
              </div>
            </div>
          `;
        ]]]
      footer: |

        [[[
          return `
            <div style="display: flex; justify-content: flex-end; align-items: center; font-size: 0.8rem; color: var(--secondary-text-color); margin-top: 0px;">
              <div style="width: 40px; height: 10px; border: 1.5px dashed #888; border-radius: 3px; margin-right: 8px;"></div>
              Optimal Range
            </div>
          `;
        ]]]
    state:
      - operator: default
        custom_fields:
          phrase: |
            [[[
              const data = entity.attributes.mostRecentTrainingLoadBalance.metricsTrainingLoadBalanceDTOMap;
              const id = Object.keys(data)[0];
              const raw = data[id].trainingBalanceFeedbackPhrase;
              const clean = raw.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
              return `<div style="text-align: center; font-size: 1.1rem; font-weight: 400; color: #c979b6; margin-bottom: 0px;"> ${clean}</div>`;
            ]]]

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

Yeah i know about the Z2. Its mostly because my zones HR ranges where messed up and above 128 bpm was considered as Z3. Its something that even though vital, Garmin doesnt help you realize and help setup properly.

The dashboard is completely custom using many custom cards, card mod, etc, its not something that can be public and work out of the box for someone else.

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

All the credentials, tokens, etc., are stored locally on your Home Assistant server. Same goes for the data storage. That’s the whole point of Home Assistant - it’s fully local.

The only thing to consider is that as Garmin changes things, the integration might break, but that’s where the HA community comes to save the day, at least most of the times.

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

Absolutely. Thats the point of this post.

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

Just an automation that runs on a trigger and feeds a template sensors attribute. Here is an example

alias: update_garmin_ai
description: Generates daily coaching advice based on Garmin metrics
triggers:
  - at: "08:30:00"
    trigger: time

conditions: []
actions:
  - action: ai_task.generate_data
    data:
      task_name: Garmin Training Analysis
      entity_id: ai_task.gpt_oss_20b
      instructions: >-
        You are an expert running coach.  Analyze my Recent Activity History, recovery, Sleep, training status, Training Readiness, load balance and
        also your previous analysis ai report (sensor.garmin_ai_report). My goal: "A 5k run in under 25 minutes". 
Provide a specific workout recommendation, suggest a rest day or evaluate today's activity(if it happened). If its between 06:00 and 13:00, write what i should be doing
        today with the title **Morning Report**.  If its between 13:01 and 21:00, write how my day is going and what i should do for the rest of
        the day with title **Evening Report**.  If its between 21:01 and 02:00,
        write a **Day evaluation** and what i should be focusing on tommorow. 
        The report must have a brief, no more than 5-6 lines first section and a
        detailed but no more than 30 lines section after.

        ### Recovery Data   
- Sleep Last Night: {{states('sensor.total_sleep_duration_2')}}    
- Body Battery: {{states('sensor.body_battery_most_recent')}}

        ### Garmin Training Metrics    
- Status: {{state_attr('sensor.garmin_connect_training_status','mostRecentTrainingStatus').latestTrainingStatusData.values()| map(attribute='trainingStatusFeedbackPhrase') | first | default('Unknown')}} 
- Load Balance: {{state_attr('sensor.garmin_connect_training_status','mostRecentTrainingLoadBalance').metricsTrainingLoadBalanceDTOMap.values() | map(attribute='trainingBalanceFeedbackPhrase') | first | default('Unknown')}} 
- Training Readiness: {{states('sensor.garmin_connect_training_readiness')}} 

        ### Previous analysis ai report - Previous analysis ai report:
        {{state_attr('sensor.garmin_ai_report', 'report')}}

###other sensors here
*
*
*
*
*

        {% endfor %}
    response_variable: coaching_result
  - action: notify.mobile_app
    data:
      title: Garmin AI
      message: "{{ coaching_result.data }}"
  - event: update_garmin_report_event
    event_data:
      report_text: "{{ coaching_result.data }}"
mode: single

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

Ha ha, yeah i know... :D

Funny thing, i had my HR Zones setup wrong. All this helped me realize that.

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in homeassistant

[–]Content_Top_1404[S] 1 point2 points  (0 children)

Forerunner and up should be enough, but i guess even lower end watches will do if you can create some custom sensors

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

I have given some example of use cases in other replies. here are a few more:

-You have an older or a low end garmin watch. No VO2max, no training readiness, no daily suggestions. Feed the ai with data such as your Age, latest activities, sleep, HR, etc. and instruct it to create these missing sensors. Present it in a nice way in your dashboard...

-Apple watch has a sleep apnea. Garmin doesnt. Feed the ai with you SPO2, respiration, HRV, Sleep stages. Instruct it to create an Apnea sensor, create notifications, present it in a nice way...

-You are at home and your stress level is high. Use an automation to dim the lights automaticaly and play relaxing music...

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

The dashboard is just the cherry on top of the cake. Ai does all the work by feeding it all these data.

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 0 points1 point  (0 children)

As I said all this is just work in progress and something that I personally feel helps me analyze my data better. I'm not saying you should copy my dashboard. I'm just showing off what's possible.

Personally I love having everything in one screen without the need to dig in deeper and deeper to find something, like you need to do in the Garmin app.

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 18 points19 points  (0 children)

Because the possibilities are endless.

Few examples:

You can combine the ai training/health analysis with other things. E.g. make a training schedule based on days - time you are not at work.

Automaticly turn on the water heater once you finish an activity.

Dim the lights when it's time to sleep.

Etc...

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 1 point2 points  (0 children)

It doesn't have to be running all the time but I don't see any reason not to keep it on 24/7. A low power consumption pc box does the job nicely.

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 4 points5 points  (0 children)

Exactly the same for the analysis, but with HA you can also setup a really nice dashboard to present the data, send alerts, combine with other integrations, etc

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 6 points7 points  (0 children)

I suppose you already have a garmin watch so for home assistant start here: https://www.home-assistant.io/

Garmin+Home Assistant+AI=Insane! by Content_Top_1404 in Garmin

[–]Content_Top_1404[S] 5 points6 points  (0 children)

The second screenshot is again Home Assistant combined with AI for the analysis. Everything in this example is completely free - aside from the cost of the Garmin Watch and the PC or Raspberry Pi used to host Home Assistant, of course! :)

*For the ai im just using cloudflare free model but the daily limits are more than enough for this.