Real-Time Sports Scores w/ TeamTracker and TeamTracker-Card (Beta)

RIght on. I consolidated some if’s too with and’s …

  - name: NFL Red Zone
    unique_id: sensor.nfl_red_zone
    state: |
            {% set redzone = namespace(teams=[]) %}
            {%- for team in integration_entities("teamtracker") -%}
            {%- if states(team) == "IN" and state_attr(team, "league") == "NFL" and state_attr(team, "team_homeaway") == "home" and state_attr(team,'possession') is not none -%}
            {% set possession = state_attr(team,'possession') %}
            {% set team_id = state_attr(team,'team_id') %}
            {% set opponent_id = state_attr(team,'opponent_id') %}
            {% set team_abbr = state_attr(team,'team_abbr') %}
            {% set opponent_abbr = state_attr(team,'opponent_abbr') %}
            {% if state_attr(team,'down_distance_text') is not none %}
            {% set down_distance_text = state_attr(team,'down_distance_text') %}
            {% set down_distance_endzone = state_attr(team,'down_distance_text').split(' ')[4] %}
            {% set down_distance_yardline = state_attr(team,'down_distance_text').split(' ')[5] %}
            {% set possession_abbr = opponent_abbr if opponent_id == possession else team_abbr %}
            {% set non_possession_abbr = opponent_abbr if opponent_id != possession else team_abbr %}
            {%- if (possession_abbr != down_distance_endzone) and (down_distance_yardline | int) <= 20 %}
            {% set redzone.teams = redzone.teams + [team] %}
            {% endif %}
            {% endif %}
            {% endif %}
            {% endfor %}
            {{ redzone.teams | count }}
    attributes:
        teams: |
            {% set redzone = namespace(teams=[]) %}
            {%- for team in integration_entities("teamtracker") -%}
            {%- if states(team) == "IN" and state_attr(team, "league") == "NFL" and state_attr(team,'possession') is not none -%}
            {% set possession = state_attr(team,'possession') %}
            {% set team_id = state_attr(team,'team_id') %}
            {% set opponent_id = state_attr(team,'opponent_id') %}
            {% set team_abbr = state_attr(team,'team_abbr') %}
            {% set opponent_abbr = state_attr(team,'opponent_abbr') %}
            {% if state_attr(team,'down_distance_text') is not none %}
            {% set down_distance_text = state_attr(team,'down_distance_text') %}
            {% set down_distance_endzone = state_attr(team,'down_distance_text').split(' ')[4] %}
            {% set down_distance_yardline = state_attr(team,'down_distance_text').split(' ')[5] %}
            {% set possession_abbr = opponent_abbr if opponent_id == possession else team_abbr %}
            {% set non_possession_abbr = opponent_abbr if opponent_id != possession else team_abbr %}
            {%- if (possession_abbr != down_distance_endzone) and (down_distance_yardline | int) <= 20 %}
            {% set redzone.teams = redzone.teams + [team] %}
            {% endif %}
            {% endif %}
            {% endif %}
            {% endfor %}
            {{ redzone.teams }}

I also added color:black in the CSS for the card in the Red Zone as one of my tablets had night mode.
In night mode the color of text turns white and the white text on red was washed out. Now it is like this:

  game_stats:
    card:
      type: custom:auto-entities
      unique: true
      show_empty: false
      card:
        type: custom:layout-card
        layout_type: masonry
      card_param: cards
      filter:
        template: |
          {%- for team in integration_entities("teamtracker") -%}
            {%- if state_attr(team, "league") == "[[sport]]" -%}
            {%- if states(team) == "[[status]]" -%}
            {%- if state_attr(team, "team_homeaway") == "home" -%}
            {%- if team in state_attr('sensor.nfl_red_zone','teams') -%}
              {{{"type": "custom:teamtracker-card",
                "entity": team,
                "card_mod": {"style": "ha-card {\n\n    color:  black; \n    background-color:  #ffcccc; \n    box-shadow: 0 0 10px 5px red;\n}\n"},
                "home_side": "right"}}},
            {%- else -%}
              {{{"type": "custom:teamtracker-card",
                "entity": team, 
                "home_side": "right"}}},
            {%- endif -%}
            {%- endif -%}
            {%- endif -%}
            {%- endif -%}
          {%- endfor -%}
        exclude:
          - entity_id: '*team_tracker*'
      sort:
        method: attribute
        attribute: date

Love this! Thanks for all of your work on it.

In case anyone wants to know, what I did for our ‘favorites’ is setup all of the sensors with all of the teams, setup a group for the teams we watch, and only limit to our group when doing the auto-entities. If/when we want to add a team to watch, I just add it to the group and it picks up everywhere.

I have to admit that it is something I use … we have wall panels in the house and winery with it showing … people love it!

I can’t get my card to show next game info, I can only get the current game info when the game has started

It depends on when the source data is updated.

They have four states. PRE, IN, POST, and unavailable.

For example, MLB data updates about 10am central time. All games are in PRE until the game starts. Then, they go to IN. After the game is over, they go to POST. If there is no game that day, they go unavailable (like your NBA one above). Rinse and repeat.

I have not paid attention to when the NFL data updates. I am guessing the next morning after the last game for the week has played.

Exactly, another way to look at it that there is only one game for a team (even a bye) in the data. In the NFL, what if a team played Friday game and the following week played the Thursday game? Just because the game ended Friday, it is not going to show the Thursday pregame yet until all games are done (after Monday night, likely Tuesday) because the team they are playing is not yet done … and so on as the team that that team played may be “locked” until they are finished. Therefore, Stats go from PRE (likely Tuesday), to IN at kickoff until finish, to POST after finish and could possibly even go NOT_FOUND should they drop POST stats but have yet to do PRE stats.

This week NFL games end today. Probably tomorrow or Tuesday ESPN will start to serve up PRE game data for the next games.

Okay. So, I want the active games on top, followed by PRE, POST, and no game.

So, I did this. Just sharing in case anyone is interested.

It sets team_sort based on state. IN,PRE = 1, POST=2, anything else=3. Then concatenates date behind that into a new namespace. Then, brings those in sorting on that attribute. All within an auto-entities card without a sort.

- type: custom:auto-entities
  card:
    type: entities
  show_empty: false
  filter:
    template: >
      {%- set ns = namespace(team_info=[]) -%} {%- for team in
      integration_entities("teamtracker") -%}
        {%- if state_attr(team, "league") == "MLB" %}
          {%- if states(team) and states(team) in "IN,PRE,POST" -%}
            {%- if state_attr(team, "team_homeaway") == "home" -%}
              {%- if states(team) in "IN,PRE" -%}
                {%- set team_order = 1 -%}
              {%- elif states(team) in "POST" -%}
                {%- set team_order = 2 -%}
              {%- endif %}
              {%- set team_date = state_attr(team, "date") if state_attr(team, "date") else "2099-12-31T23:00Z" %}
              {%- set ns.team_info = ns.team_info + [{"team_sensor": team, "team_order": team_order ~ state_attr(team, "date")}] -%}
            {%- endif %}
          {%- else -%}
            {%- set team_order = 3 -%}
            {%- set team_date = state_attr(team, "date") if state_attr(team, "date") else "2099-12-31T23:00Z" %}
            {%- set ns.team_info = ns.team_info + [{"team_sensor": team, "team_order": team_order ~ state_attr(team, "date")}] -%}
          {%- endif %}
        {%- endif -%}
      {%- endfor -%} {%- for team_ns in ns.team_info |
      sort(attribute="team_order") -%}
        {%- set team = team_ns.team_sensor -%}
        {%- if team in state_attr('sensor.nfl_red_zone','teams') -%}
          {{{"type": "custom:teamtracker-card",
            "entity": team,
            "card_mod": {"style": "ha-card {\n    background-color:  #ffcccc; \n    box-shadow: 0 0 10px 5px red;\n}\n"},
            "home_side": "right"}}},
        {%- else -%}
          {{{"type": "custom:teamtracker-card",
            "entity": team, 
            "home_side": "right"}}},
        {%- endif -%}
      {%- endfor -%}
    exclude:
      - entity_id: '*team_tracker*'

And, for those using favorites group.

- type: custom:auto-entities
  card:
    type: entities
  show_empty: false
  filter:
    template: >
      {%- set ns = namespace(items=[]) -%}
      {%- for team in state_attr('group.team_favorites','entity_id') -%}
        {%- if state_attr(team, "league") == "MLB" -%}
          {%- if states(team) in "IN,PRE" -%}
            {%- set team_order = 1 -%}
          {%- elif states(team) in "POST" -%}
            {%- set team_order = 2 -%}
          {%- else -%}
            {%- set team_order = 3 -%}
          {%- endif -%}
          {%- set event_name = state_attr(team, "event_name") if state_attr(team, "event_name") else state_attr(team, "team_abbr") -%}
          {%- set ns.items = ns.items + [{"team_sensor": team, "event_name": event_name, "team_order": team_order ~ state_attr(team, "date")}] -%}
        {%- endif -%}
      {%- endfor -%}
      {%- for team_ns in ns.items | unique(attribute="event_name") | sort(attribute="team_order") -%} 
        {%- set team = team_ns.team_sensor -%}
        {%- if team in state_attr('sensor.nfl_red_zone','teams') -%}
          {{{"type": "custom:teamtracker-card",
            "entity": team,
            "card_mod": {"style": "ha-card {\n    background-color:  #ffcccc; \n    box-shadow: 0 0 10px 5px red;\n}\n"},
            "home_side": "right"}}},
        {%- else -%}
          {{{"type": "custom:teamtracker-card",
            "entity": team, 
            "home_side": "right"}}},
        {%- endif -%}
      {%- endfor -%}
    exclude:
      - entity_id: '*team_tracker*'

Yes, I have the red zone check in there. It is in a decluttering card.

Change

{%- if state_attr(team, "league") == "MLB" -%}

to

{%- if state_attr(team, 'league') == '[[league]]' -%}

You could do a decluttering card for NFL and another for not NFL to save that search. But, it seems to be working as is.

1 Like

Note for anyone using the tabbed view. Recent release of Home Assistant broke the tabbed view card. They are working on it.

PS: GO LIONS! What a game for my Detroit peeps!!

1 Like

Has ESPN changed their API recently? The API Explorer link in the Wiki is giving me a 404 and on the British ESPN page that I’m being redirected to, the URL for the English Premier League is formated as “football/league/_/name/ENG.1/english-premier-league”. I can’t even find German Zweite Bundesliga.

ESPN shut down the API Explorer link a while ago but the APIs are still there. Both are supported natively w/o needing a custom API configuration.

EPL: http://site.api.espn.com/apis/site/v2/sports/soccer/eng.1/scoreboard
Bundsliga: http://site.api.espn.com/apis/site/v2/sports/soccer/ger.1/scoreboard

1 Like

Thank you so much! I’m afraid I need ger.2,because my team basically sucks. But I’ve just tried and that works perfectly.

I’m going to jump in to ask for some help too. I just discovered that the API explorer is gone and I’m having no luck finding my team.

The Charlotte 49ers College Basketball team has switched to the NCAAF and I haven’t found the magic combination to bring up their data. Any pointers.

Thanks!

For college football and basketball, you need to specify a conference id unless your team is ranked. Conference USA is 12 for football, 11 for basketball.

Thank you!

I’ve been using a version of this to track the MLB standings for the AL East all season. It has worked flawlessly for the entire season until the last week or so. Now, it seems the data structure has changed and the values no longer match the columns in my table - but only for some of the teams. Have you noticed the same issue? And do you have any advice for how to adjust for this?

Can you send an image of what you are seeing?
Mine seems to be fine … so possibly you have some older code.

One possibility is that you still use the positional way to obtain stats. Once teams start clinching, the positions of the data changes so things like x.stats[10] will not work… I use decluttering and this is what I use for MLB you can see I use x.stats.find(y=>y.abbreviation == 'AWAY').displayValue:

  mlb_settings:
    card:
      type: custom:flex-table-card
      title: '[[title]]'
      css:
        table+: 'padding: 0px; width: 1600px;'
        tbody tr td:first-child: 'width: 20%;'
        tbody tr td:nth-child(n+2): 'width: 5%;'
        tbody tr:hover: 'background-color: green!important; color:white!important;'
        tbody tr td:nth-child(5): 'background-color: green; color: white;'
      card_mod:
        style:
          .: |
            ha-card {
              overflow: auto;
              }
          $: |
            .card-header {
               padding-top: 6px!important;
               padding-bottom: 4px!important;
               font-size: 14px!important;
               line-height: 14px!important;
               font-weight: bold!important;
             }
      entities:
        include: '[[entity]]'
      sort_by: entries-
      columns:
        - hidden: true
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'PCT').value
        - name: Team
          data: entries
          modify: >-
            '<div><img src="' + x.team.logos[0].href + '" style="height:
            20px;vertical-align:middle;">&nbsp;' + x.team.displayName + '</div>'
        - name: GP
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'GP').displayValue
        - name: W
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'W').displayValue
        - name: L
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'L').displayValue
        - name: PCT
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'PCT').value.toFixed(2)
        - name: GB
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'GB').displayValue
        - name: HOME
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'Home').displayValue
        - name: AWAY
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'AWAY').displayValue
        - name: RS
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'RS').displayValue
        - name: RA
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'RA').displayValue
        - name: DIFF
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'DIFF').displayValue
        - name: L10
          data: entries
          modify: x.stats.find(y=>y.shortDisplayName == 'L10').displayValue
        - name: STRK
          data: entries
          modify: x.stats.find(y=>y.abbreviation == 'STRK').displayValue

That is precisely what I’m doing, and I figured it had something to do with approaching the end of the regular season. Here is what I’m seeing:

I don’t remember whose code I coped, and I didn’t know there was “newer code”, but here is what I have:

decluttering_templates:
  mlb_settings:
    card:
      type: custom:flex-table-card
      title: '[[title]]'
      css:
        table+: 'padding: 0px; width: 1000px;'
        tbody tr td:first-child: 'width: 12%;'
        tbody tr td:nth-child(n+2): 'width: 5%;'
        tbody tr:hover: 'background-color: lightgreen!important;'
      card_mod:
        style: |
          ha-card {
              overflow: none;
              box-shadow: none;  
              background: none;
              border: none;
            }        
      entities:
        include: '[[entity]]'
      sort_by: entries-
      columns:
        - hidden: true
          data: entries
          modify: x.stats[16].value
        - name: Team
          data: entries
          modify: x.team.displayName
        - name: PLAYED
          data: entries
          modify: x.stats[7].displayValue
        - name: WON
          data: entries
          modify: x.stats[18].displayValue
        - name: LOST
          data: entries
          modify: x.stats[9].displayValue
        - name: PCT
          data: entries
          modify: x.stats[17].displayValue
        - name: GB
          data: entries
          modify: x.stats[6].displayValue
        - name: HOME
          data: entries
          modify: x.stats[33].displayValue
        - name: AWAY
          data: entries
          modify: x.stats[34].displayValue
        - name: FOR
          data: entries
          modify: x.stats[14].displayValue
        - name: AGAINST
          data: entries
          modify: x.stats[13].displayValue
        - name: DIFF
          data: entries
          modify: x.stats[11].displayValue
        - name: L10
          data: entries
          modify: x.stats[37].summary
        - name: STREAK
          data: entries
          modify: x.stats[15].displayValue

I’ll take a look at your code and se if I can figure out what I need to change. Thanks!

Yes, that is the older code.
If you give me a few hours, I will add the CLINCH flag display. I did it for NHL, it is pretty straightforward. For information, that data is “z”, “e” stuff here:

A while back, @kbrown01 changed from using the numbers to using the abbreviations:
x.stats.find(y=>y.abbreviation == ‘L’).displayValue