Garmin LiveTrack tracking package 🗺️

I want to share a package file for integrate information from a Garmin LiveTrack event.

I use the IMAP sensor get the url automatic. IMAP integration need to fetch the first 21000 bytes because where the link is placed in the mail.

It’s also possible to share the url into the input field. input_text.garmin_livetrack_link

garmin_livetrack.yaml package file

# Garmin Livetrack

# homeassistant:
#    packages: !include_dir_named packages

# packages/garmin_livetrack.yaml

input_text:
  garmin_livetrack_link:
    name: Garmin LiveTrack link
    max: 250
    icon: mdi:link
    initial: ""


rest:
- resource: "https://livetrack.garmin.com/apollo/graphql"
  scan_interval: 2592000  #30 d (we update it manually in automation)
  method: POST
  headers:
    Content-Type: application/json
  payload_template: >
    {{ {
      'query': "query sessionAndPoints($sessionId:String!,$token:String!,$begin:String){\n"
               " session:sessionById(sessionId:$sessionId,token:$token){start end sessionId}\n"
               " points:trackPointsBySessionId(sessionId:$sessionId,token:$token,begin:$begin){\n"
               "  trackPoints{fitnessPointData{totalDurationSecs speedMetersPerSec totalDistanceMeters activityType cadenceCyclesPerMin distanceMeters durationSecs elevationGainMeters elevationSource eventTypes heartRateBeatsPerMin pointStatus powerWatts} position{lat lon} dateTime speed altitude}}\n"
               "}",
      'variables': {
        'sessionId': states('sensor.garmin_livetrack_id'),
        'token'    : state_attr('sensor.garmin_livetrack_id','token'),
        'begin'    : [states('sensor.garmin_livetrack_points_rest'), state_attr('sensor.garmin_livetrack_session_rest','start')] | select | max
                     if states('sensor.garmin_livetrack_id') == state_attr('sensor.garmin_livetrack_session_rest','sessionId')
                     else none
      }
    } | to_json }}
  sensor:
      #used to get last point info. Using begin we only fetch points >= to last (must be >= start or null)
    - name: "Garmin LiveTrack Points Rest"
      unique_id: garmin_livetrack_points_rest
      value_template: >-
        {{ value_json.data.points.trackPoints[-1].dateTime if (value_json.data.points.trackPoints | count) }}"
      json_attributes_path: "$.data.points.trackPoints[-1:]"
      json_attributes:
        - dateTime
        - position
        - altitude
        - fitnessPointData
        - speed
  
      #used to get session start/end times and to determine if session is in_progress (if end is start + 1 day)
    - name: "Garmin LiveTrack Session Rest"
      unique_id: garmin_livetrack_session_rest
      value_template: >-
        {%- set s = value_json.data.session %}
        {%- if s and s.start and s.end %}
          {{ as_timestamp(s.end) == as_timestamp(s.start) + 86400 }}
        {%- else %}
          False
        {%- endif %}
      json_attributes_path: "$.data.session"
      json_attributes:
        - start
        - end
        - sessionId
          

template:
- trigger:
  - platform: state
    entity_id: input_text.garmin_livetrack_link
  - platform: state
    entity_id: sensor.garmin_livetrack_link_from_email 
  sensor:
  - name: Garmin LiveTrack id
    state: >-
      {%- if "livetrack.garmin.com" in trigger.to_state.state -%}
      {{- (trigger.to_state.state | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0] -}}
      {%- elif trigger.entity_id == "sensor.garmin_livetrack_link_from_email" and trigger.to_state.state != "" -%}
      {{- trigger.to_state.state -}}
      {%- endif -%}
    attributes:
      link: >-
        https://livetrack.garmin.com/session/
        {%- if "livetrack.garmin.com" in trigger.to_state.state -%}
        {{- (trigger.to_state.state | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0] -}}
        {%- elif trigger.entity_id == "sensor.garmin_livetrack_link_from_email" and trigger.to_state.state != "" -%}
        {{- trigger.to_state.state -}}/token/{{- trigger.to_state.attributes.token -}}
        {%- endif -%}
      session: >-
        {%- if "livetrack.garmin.com" in trigger.to_state.state -%}
        {{- (trigger.to_state.state | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0].0 -}}
        {%- elif trigger.entity_id == "sensor.garmin_livetrack_link_from_email" and trigger.to_state.state != "" -%}
        {{- trigger.to_state.attributes.session -}}
        {%- endif -%}
      token: >-
        {%- if "livetrack.garmin.com" in trigger.to_state.state -%}
        {{- (trigger.to_state.state | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0].1 -}}
        {%- elif trigger.entity_id == "sensor.garmin_livetrack_link_from_email" and trigger.to_state.state != "" -%}
        {{- trigger.to_state.attributes.token -}}
        {%- endif -%}
    icon: mdi:map


# Sensor to get link from email with integration: https://www.home-assistant.io/integrations/imap_email_content/
- trigger:
  - platform: event
    event_type: "imap_content"
    event_data:
      sender: [email protected]
  sensor:
  - name: Garmin LiveTrack link from email
    state: >-
      {{ (trigger.event.data['text'] | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token', ignorecase=True))[0] | replace('\n','') | replace('\r','') | replace('=','') }}
    attributes:
      date: "{{ trigger.event.data['date'] }}"
      link: >-
        https://livetrack.garmin.com/session/
        {{- ((trigger.event.data['text'] | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0].0) | replace('\n','') | replace('\r','') | replace('=','') }}
        {{- '/token/' }}
        {{- ((trigger.event.data['text'] | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0].1) | replace('\n','') | replace('\r','') | replace('=','') }}
      session: >-
        {{ ((trigger.event.data['text'] | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0].0) | replace('\n','') | replace('\r','') | replace('=','') }}
      token: >-
        {{ ((trigger.event.data['text'] | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token/([a-z0-9-\n\r=][^"]+)', ignorecase=True))[0].1) | replace('\n','') | replace('\r','') | replace('=','') }}


- binary_sensor:
  - name: Garmin Livetrack
    state: "{{ is_state_attr('automation.garmin_livetrack', 'current',1) }}"
    attributes:
      latitude: "{{ trigger.to_state.attributes.position.lat }}"
      longitude: "{{ trigger.to_state.attributes.position.lon }}"
      start: "{{ state_attr('sensor.garmin_livetrack_session_rest','start') }}"
      datetime: "{{ trigger.to_state.attributes.dateTime }}"
      duration: "{{ timedelta(seconds=(float(trigger.to_state.attributes.fitnessPointData.totalDurationSecs,0))) }}"
      durationSec: "{{ float(trigger.to_state.attributes.fitnessPointData.totalDurationSecs,0) }}"
      activity: "{{ trigger.to_state.attributes.fitnessPointData.activityType | lower }}"
      speed: "{{ trigger.to_state.attributes.speed }}"
      speed_ms: "{{ trigger.to_state.attributes.fitnessPointData.speedMetersPerSec }}"
      pace: "{{ 16.666666667/float(trigger.to_state.attributes.speed,0) }}"
      altitude: "{{ trigger.to_state.attributes.altitude }}"
      elevation_gain: "{{ int(trigger.to_state.attributes.fitnessPointData.elevationGainMeters,0) }}"
      elevation_source: "{{ trigger.to_state.attributes.fitnessPointData.elevationSource }}"
      distance: "{{ int(trigger.to_state.attributes.fitnessPointData.distanceMeters,0) /1000 | round(1) }}"
      heartbeats: "{{ trigger.to_state.attributes.fitnessPointData.heartRateBeatsPerMin }}"
      watt: "{{ trigger.to_state.attributes.fitnessPointData.powerWatts }}"
      event: "{{ trigger.to_state.attributes.fitnessPointData.eventTypes }}"
      status: "{{ trigger.to_state.attributes.fitnessPointData.pointStatus | lower }}"
      link: "{{ state_attr('sensor.garmin_livetrack_id','link') }}"

    
automation:
- alias: Garmin LiveTrack new link
  id: "garmin_livetrack_new_link"
  mode: restart
  trigger:
  - platform: state
    entity_id: sensor.garmin_livetrack_id
  condition:
    - condition: template
      value_template: "{{ trigger.to_state.state != '' }}"
    - condition: template
      value_template: "{{ trigger.to_state.state != 'unknown' }}"
    - condition: template
      value_template: '{{ trigger.from_state.state != trigger.to_state.state }}'
  action:
  - variables:
      update_min: 2
  - alias: "Test if any data is present"
    repeat:
      until:
      - condition: or
        conditions:
        - condition: template
          value_template: "{{ as_timestamp(now()) - as_timestamp(states('sensor.garmin_livetrack_points_rest'), now()) > 0 }}"
        - condition: template
          value_template: "{{ repeat.index >= 15 }}"
      sequence:
      - alias: "Update sensor.garmin_livetrack_points_rest"
        service: homeassistant.update_entity
        entity_id: sensor.garmin_livetrack_points_rest
      - alias: "Wait 15 sec"
        delay: "00:00:15"
  - repeat:
      until:
      - condition: or
        conditions:
          - alias: "Test if END in eventTypes"
            condition: template
            value_template: "{{ 'END' in state_attr('sensor.garmin_livetrack_points_rest','fitnessPointData')['eventTypes'] if state_attr('sensor.garmin_livetrack_points_rest','fitnessPointData') }}"
          - alias: "Test if last update update > 30min"
            condition: template
            value_template: "{{ as_timestamp(now())-int(as_timestamp(states('sensor.garmin_livetrack_points_rest'),as_timestamp(now())),0) > 60*60 }}"
          - alias: "Test if new id"
            condition: template
            value_template: "{{ trigger.to_state.state != states('sensor.garmin_livetrack_id') }}"
          - alias: "Test if loop run for 11h"
            condition: template
            value_template: "{{ repeat.index > (11*60/update_min) }}"
      sequence:
      - service: homeassistant.update_entity
        entity_id: sensor.garmin_livetrack_points_rest
      - delay:
          minutes: "{{ update_min }}"
      


# Custom notification
- alias: Garmin LiveTrack start
  id: "garmin_livetrack_start"
  mode: restart
  trigger:
  - platform: state
    entity_id: binary_sensor.garmin_livetrack
    to: "on"
  action:
  - alias: "Notify start"
    service: notify.mobile_app_user
    data:
        title: "Garmin LiveTrack"
        message: "Startar"
        data:
          ledColor: green
          color: green
          group: Garmin
          tag: garmin_livetrack
          sticky: true
          clickAction: "entityId:binary_sensor.garmin_livetrack"
          timeout: 270
          notification_icon: mdi:map-clock
          actions:
          - action: "URI"
            uri: "{{ state_attr('sensor.garmin_livetrack_id','link') }}"
            title: "Visa Garmin LiveTrack"
  - alias: "Turn on automations with actions depending on Livetrack"
    service: automation.turn_on
    entity_id: 
    - automation.garmin_livetrack_plats
  - wait_for_trigger:
    - trigger: state
      entity_id: binary_sensor.garmin_livetrack
      to: "off"
  - alias: "Notify end"
    service: notify.mobile_app_user
    data:
        title: "Garmin Livetrack"
        message: |
          Slut!
          {{ state_attr('binary_sensor.garmin_livetrack','duration') }}
          {{ state_attr('binary_sensor.garmin_livetrack','distance') }}
          {{ state_attr('binary_sensor.garmin_livetrack','activity') }}
          {{ state_attr('binary_sensor.garmin_livetrack','event') }}
        data:
          group: Garmin
          tag: garmin_livetrack
          clickAction: "entityId:binary_sensor.garmin_livetrack"
          timeout: 300
          notification_icon: mdi:stop-circle
          actions:
          - action: "URI"
            uri: "{{ state_attr('sensor.garmin_livetrack_id','link') }}"
            title: "Visa Garmin LiveTrack"
  - service: automation.turn_off
    entity_id: 
    - automation.garmin_livetrack_plats


#logger:
#  filters:
#    homeassistant.components.rest.sensor:
#    - "JSON result was not a dictionary or list with 0th element a dictionary"

Good luck! :man_running::dash::japan::world_map:

3 Likes

Wow! this project is great! I test it tomorrow. Thanks :wink:

I added the package, could you explain how to display the data in the dashboard? Thank you

You could lockup binary_sensor.garmin_livetrack in the map or put it in a dashboard map.
Or use the data to activate som automation.

1 Like

I have 2 warning:
Template variable warning: ‘value_json’ is undefined when rendering ‘{{ value_json.trackPoints[-1].dateTime if ‘trackPoints’ in value_json and (value_json.trackPoints | length) > 0 }}’

  • Template variable warning: ‘None’ has no attribute ‘attributes’ when rendering ‘{{ (state_attr(‘sensor.homeassistant’,‘body’) | regex_findall(find=‘https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token’, ignorecase=True))[0] | replace(’\n’,’’) | replace(’\r’,’’) | replace(’=’,’’) if ‘body’ in states.sensor.homeassistant.attributes }}’
  • Template variable warning: ‘None’ has no attribute ‘attributes’ when rendering ‘{{ ((state_attr(‘sensor.homeassistant’,‘body’) | regex_findall(find=‘Garmin Live Tracking - Powered by Garmin Connect™’, ignorecase=True))[0]) | replace(’\n’,’’) | replace(’\r’,’’) | replace(’=’,’’) if ‘body’ in states.sensor.homeassistant.attributes }}’

Do you use the IMAP sensor?
Otherwise you could remove the senor:

- sensor:
  - name: Garmin LiveTrack link from email
    state: >-
      {{ (state_attr('sensor.homeassistant','body') | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+)/token', ignorecase=True))[0] | replace('\n','') | replace('\r','') | replace('=','')
         if 'body' in states.sensor.homeassistant.attributes }}
    attributes:
      token: >-
        {{ ((state_attr('sensor.homeassistant','body') | regex_findall(find='https://livetrack.garmin.com/session/([a-z0-9-\n\r=]+/token/+[a-z0-9-\n\r=][^"]+)', ignorecase=True))[0]) | replace('\n','') | replace('\r','') | replace('=','')
         if 'body' in states.sensor.homeassistant.attributes }}

Yes, I have IMAP sensor

Change sensor.homeassistant to the name of your sensor.

Nothing to do, in my case it doesn’t work.
It doesn’t fetch the link from the mail and it doesn’t show tracking, even if the imap sensor correctly shows the arrival of a new mail

Nice package @fatuuse ! Helped me a lot for what I planned.
Now I cast the livetrack web to a nest hub and show a phone notification linked to a lovelace card with some info and a direct gmaps link to my location.

Thanks for sharing

1 Like

I updated the post with new settings to use with the new IMAP integration that uses events.

1 Like

Hi, I have a problem with the link to display,
I attach the working link that is generated in the email and the one that gets me the sensor.garmin_livetrack_id sensor

as you can see the part is missing /token/BCF8F228A199A28673073EC67383DE is missing
which is replaced by trackpoints

from garmin mail:    https://livetrack.garmin.com/session/2818c519-4fa8-47c0-ac6e-bd1f4e661d09/token/BCF8F228A199A28673073EC67383DE
form package sensor: https://livetrack.garmin.com/session/2818c519-4fa8-47c0-ac6e-bd1f4e661d09/trackpoints

also as you can see from the sensors list

  • token value is missing
  • some tracking attributes are missing

Is there something wrong with me or something is missing in the sensor syntax?

thanks

Since you stopped the recording your activity you got the END event and no more speed data will populate.

From what I can see, it look like it works.

Token is not needed for this, just for look at Garmin map. I will see if I could correct the regex for it.

OK thanks,
it would be really helpful at least for me, i’d like to open the link in the wall tablet, so they know where i am.
I’m trying to figure out how to extract from the mail but I don’t have knowledge of regex filters but I still want to try.

I see!
Right now the binary_sensor.garmin_livetrack will include lat and long from your watch and shows up in HA map, and you can create a map in any dashboard
including that entity.

a card in the dashboard could be a solution, but I see the link to the original page as more practical and nicer for those who open it

thanks again

I updated the templates sensors in the first post.
I hope it works! :+1:

1 Like

Perfect, I tried and it works perfectly, now it gives me the token.

A last question,
I’m not a programmer but self-taught, I tried to search but there are too many results and I don’t know which ones are the most valid for my limited knowledge.
I ask if you can point me to a “guide” to understand how to extract values from the mail body for a job like the one you did

anyway, thanks a lot.

You could try this: https://regex-generator.olafneumann.org

thanks for the reply.