Thank you so much, really. I definitely want to know how to get deeper into all this. The only info I want is in the provided API so I will get this all set up!
@kbrown01 - how often have you seen the starting goalies link change? Is it daily/weekly/season?
Here is what I have for todayās but curious if it is worth chasing this if it changes daily?
resource: https://www.dailyfaceoff.com/_next/data/ZfwQeh4nYUKWDEymsct39/starting-goalies.json
Why I ask is that there is a field homeNewsFantasyDetails that has a few notes and kind of plays well with a scrolling marquee. more for s&gās than anything else, but was also thinking about piping it out to a scrolling LED marquee
So the first CFP was posted yesterday. If you are using my Top 25 Code you probably saw the update.
Here are the rough matchups and playoff data: The top four conference champions in the final rankings will receive a bye. First-round games will be played on campus sites with No. 5 hosting No. 12, No. 6 facing No. 11, No. 7 meeting No. 10 and No. 8 squaring off with No. 9.
Winners of those games will advance to the quarterfinals at the Fiesta, Sugar, Peach and Rose bowls. Those games will be played either Dec. 31 or Jan. 1. The Orange Bowl and Cotton Bowl will host the semifinals on Jan. 9 and Jan. 10, respectively.
The championship game will be played on Jan. 20 in Atlanta at Mercedes-Benz Stadium.
For fun I applied what I did in the Soccer code to CFP.
- Variables added for Bye and teams 5-12. Colors matchup to who would play each other if it ended today. I believe that there will be 5 CFPās.
- If the variable isnāt added or has a 0 nothing is applied.
Here is a quick snapshot and yes I will fix the color or @ehcah/anyone else which are more adept in that than I am. Iāll throw the code below as well.
Here are the changes to the Top 25 Decluttering Card
ncaa_top25_settings:
card:
type: custom:flex-table-card
title: '[[title]]'
css:
table+: 'padding: 0px; width: 100%;border-collapse: collapse; margin-top:12px;'
tbody tr td:first-child: 'width: 1%;'
tbody tr td:nth-child(2): 'width: 2%;'
tbody tr td:nth-child(3): 'width: 1%;'
tbody tr td:nth-child(4): 'width: 2%;'
tbody tr td:nth-child(n+5): 'width: .5%;'
tbody tr:nth-child([[bye]]): 'border-bottom: 2px solid crimson!important;'
tbody tr:nth-child([[team5]]): 'background-color: dodgerblue; color: white;'
tbody tr:nth-child([[team6]]): 'background-color: navy; color: white;'
tbody tr:nth-child([[team7]]): 'background-color: #43C6DB; color: white;'
tbody tr:nth-child([[team8]]): 'background-color: #357EC7; color: white;'
tbody tr:nth-child([[team9]]): 'background-color: #357EC7; color: white;'
tbody tr:nth-child([[team10]]): 'background-color: #43C6DB; color: white;'
tbody tr:nth-child([[team11]]): 'background-color: navy; color: white;'
tbody tr:nth-child([[team12]]): 'background-color: dodgerblue; color: white;'
tbody tr:hover: 'background-color: dimgrey!important; color:white!important;'
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;
}
sort_by: entries+
entities:
include: '[[entity]]'
exclude: '[[excluded_entities]]'
columns:
- name: Rank
data: '[[attribute]]'
modify: x.current
- name: Up-Down
data: '[[attribute]]'
modify: |-
if (x.current == x.previous)
'<ha-icon icon="mdi:minus" >'
else if (x.current =="0")
'<ha-icon icon="mdi:bomb" style="color:red;" >'
else if (x.previous == "0")
'<div><ha-icon icon="mdi:hand-clap" style="color:blue;"></ha-icon> ' + "New " + '</div>';
else if (x.current > x.previous)
'<div><ha-icon icon="mdi:arrow-down" style="color:red;"></ha-icon> ' + "Dn "+ (x.current - x.previous) + '</div>';
else if (x.current < x.previous)
'<div><ha-icon icon="mdi:arrow-up" style="color:green;"></ha-icon> ' + "Up "+ (x.previous - x.current) + '</div>';
- name: Record
data: '[[attribute]]'
modify: x.recordSummary
- name: Team
data: '[[attribute]]'
modify: >-
'<div><img src="' + x.team.logos[0].href + '" style="height:
20px;vertical-align:middle;"> ' + x.team.nickname + '</div>'
- name: 1st Place Votes
data: '[[attribute]]'
modify: x.firstPlaceVotes
- name: Previous Rank
data: '[[attribute]]'
modify: x.previous
- name: Points
data: '[[attribute]]'
modify: x.points
Here is what the card call looks like:
- attributes:
label: Top 25 Standings (AP-Coaches-FCS Polls)
icon: mdi:account-group
card:
type: horizontal-stack
cards:
- type: vertical-stack
cards:
- type: markdown
content: >
<h4 style="text-align:center;"> {{
state_attr('sensor.ncaaf_rank',
'rankings')[0]['headline'] }} {{
state_attr('sensor.ncaaf_rank',
'rankings')[0]['shortHeadline'] }}-
{{as_timestamp(state_attr('sensor.ncaaf_rank','rankings')[0]['date'])
| timestamp_custom('%m/%d/%Y') }} </h4>
- type: custom:decluttering-card
template: ncaa_top25_settings
variables:
- title: ''
- entity: sensor.ncaaf_poll_ap_top_25_all
- attribute: ranks
- bye: 4
- team5: 5
- team6: 6
- team7: 7
- team8: 8
- team9: 9
- team10: 10
- team11: 11
- team12: 12
So I am hoping someone can help because my head hurts - I know this is a TL;DR sorry about thatā¦ I have been trying to figure out the playoffs for NCAAF since the CFP was released two weeks ago. My sensor above is incorrect. and I am trying to figure out how to create the code to grab the top 4 conference teams by rank (ESPN has a SEED variable but it doesnāt correspond to rankings which I was hoping it would). So the rankings now is broken into 6 sections - CFP, AP, Coaches,FCS Coaches, Div II and Div III.(0-5) So I created a new sensor specifically for the CFP
################################################ CFP Poll #####################################
##NCAA Men's Football CFP
- platform: rest
scan_interval: 86400
name: ncaaf_poll_cfp_top_25_all
unique_id: sensor.ncaaf_poll_cfp_top_25_all
resource: https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings?seasontype=2&type=0&level=3
value_template: "{{ now() }}"
json_attributes_path: "$.['rankings'][0]"
json_attributes:
- ranks
- droppedOut
- others
Iāve created a playoff skeleton that will evolve but just starting out - here is the first pass
What I am having a heck of a time with is trying to stack rank the teams based on the CFP rankings and the CFP format which is this:
-
The 12 participating teams will be the five conference champions ranked highest by the CFP selection committee, plus the next seven highest-ranked teams.
-
The ranking of the teams will continue to be done by a selection committee.
-
The four highest-ranked conference champions will be seeded one through four and will receive a first-round bye. The fifth conference champion will be seeded where it was ranked or at No. 12 if it is outside the top 12 rankings. Non-conference champions ranked in the top four will be seeded beginning at No. 5. Because of this, the seeding, 1 through 12, could look different than the final rankings.
-
The eight teams seeded No. 5-12 will play in a first round with the higher seeds hosting the lower seeds either on campus or at other sites designated by the higher-seeded institution (No. 12 at No. 5, No. 11 at No. 6, No. 10 at No. 7 and No. 9 at No. 8.).
-
The selection committee will assign the four highest-ranked conference champions to Playoff Quarterfinals hosted by bowls. This will be done in consideration of historic bowl relationships, then in consideration of rankings. For example, if the Sugar Bowl hosts a Playoff Quarterfinal and the SEC champion is ranked No. 1 and the Big 12 champion is ranked No. 3, the SEC champion would be assigned to the Sugar Bowl and the Big 12 champion would be assigned elsewhere.
-
With the four highest-ranked conference champions assigned to bowls, their four Playoff Quarterfinal opponents will be dictated by the bracket (i.e., No. 1 vs. No. 8/9 winner, No. 4 vs. No. 5/12 winner, No. 2 vs. 7/10 winner; No. 3 vs. 6/11 winner.)
-
The College Football Playoff bracket will follow the selection committeeās rankings, with no modifications made to avoid rematches between teams that may have played during the regular season or are from the same conference.
-
The bracket will remain in effect throughout the playoff (i.e., no re-seeding).
-
The highest seed will receive preferential placement for the Playoff Semifinal bowl assignment.
I have created templates for each conference to grab any team that is in the top 25 in that conference - here is an example
- name: ncaaf_cfp_american_athletic
unique_id: sensor.ncaaf_cfp_american_athletic
state: "{{ now() }}"
attributes:
cfp_eligible: >
{% set entries = state_attr('sensor.ncaaf_conference_american_athletic', 'entries') %}
{% set result = namespace(teams=[]) %}
{% for entry in entries %}
{% if entry.team.rank is defined and entry.team.rank <= 25 %}
{% set seed = entry.stats | selectattr('name', 'equalto', 'playoffSeed') | map(attribute='displayValue') | first %}
{% set team_data = {
'name': entry.team.name,
'abbreviation': entry.team.abbreviation,
'shortDisplayName': entry.team.shortDisplayName,
'logo': entry.team.logos[0].href,
'seed': seed,
'rank': entry.team.rank
} %}
{% set result.teams = result.teams + [team_data] %}
{% endif %}
{% endfor %}
{{ result.teams| tojson }}
I was then thinking that I would put them in order and then just have the playoff bracket refer to this sensor but it hasnāt worked so far.
- name: ncaaf_cfp_top_teams
unique_id: sensor.ncaaf_cfp_top_teams
state: "{{ now() }}"
attributes:
cfp_order: >
{% set conferences = ['sensor.ncaaf_cfp_atlantic_coast', 'sensor.ncaaf_cfp_big_12', 'sensor.ncaaf_cfp_big_ten', 'sensor.ncaaf_cfp_southeastern'] %}
{% set other_conferences = ['sensor.ncaaf_cfp_atlantic_coast', 'sensor.ncaaf_cfp_big_12', 'sensor.ncaaf_cfp_big_ten', 'sensor.ncaaf_cfp_southeastern'] %} {% set other_conferences = ['sensor.ncaaf_cfp_american_athletic', 'sensor.ncaaf_conference_usa', 'sensor.ncaaf_cfp_fbs_independents', 'sensor.ncaaf_mid_american', 'sensor.ncaaf_mountain_west', 'sensor.ncaaf_pac12', 'sensor.ncaaf_sun_belt_west', 'sensor.ncaaf_sun_belt_east'] %}
{% set top_teams = namespace(teams=[]) %}
{% for conference in conferences %}
{% set entries = state_attr(conference, 'cfp_eligible') %}
{% if entries %}
{% for entry in entries %}
{% if entry.seed == '1' %}
{% set team_data = {
'name': entry.name,
'abbreviation': entry.abbreviation,
'shortDisplayName': entry.shortDisplayName,
'logo': entry.logo,
'rank': entry.rank
} %}
{% set top_teams.teams = top_teams.teams + [team_data] %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% set lowest_ranked_team = None %}
{% for conference in other_conferences %}
{% set entries = state_attr(conference, 'cfp_eligible') %}
{% if entries %}
{% for entry in entries %}
{% if entry.seed == '1' and (lowest_ranked_team is none or entry.rank < lowest_ranked_team.rank) %}
{% set lowest_ranked_team = {
'name': entry.name,
'abbreviation': entry.abbreviation,
'shortDisplayName': entry.shortDisplayName,
'logo': entry.logo,
'rank': entry.rank
} %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% if lowest_ranked_team is not none %}
{% set top_teams.teams = top_teams.teams + [lowest_ranked_team] %}
{% endif %}
{% set sorted_teams = top_teams.teams | sort(attribute='rank') %}
{{ {'entries': sorted_teams} | tojson }}
I also toyed with using the conference Idās and just focusing on the sensor.ncaaf_cfp_top_teams with something like this but hasnāt worked yet - just in the template tool for now
{% set groups = [4, 8, 1, 5] %}
{% set entries = state_attr('sensor.ncaaf_poll_cfp_top_25_all', 'ranks') %}
{% set result = namespace(teams=[]) %}
{% for group in groups %}
{% set top_team = None %}
{% for entry in entries %}
{% if entry.team.groups.id | int == group %}
{% if top_team is none or entry.current < top_team.current %}
{% set top_team = entry %}
{% endif %}
{{ entry }}
{% endif %}
{% endfor %}
{% if top_team is not none %}
{% set team_data = {
'current_rank': top_team.current,
'team_name': top_team.team.name,
'team_nickname': top_team.team.nickname,
'team_abbreviation': top_team.team.abbreviation,
'team_color': top_team.team.color,
'team_logo': top_team.team.logos[0].href,
'team_conference': top_team.team.conference,
'team_group_id': top_team.team.groups.id
} %}
{% set result.teams = result.teams + [team_data] %}
{% endif %}
{% endfor %}
Is there an better approach that I have been blinded to by going down this rabbit hole?
Dude this is absolute crazy stuff right here!!
Sure it might not be easy to understand the code, but it seems to spit out the exact right information.
Iāll try to add it to my board within the next days, so I might find something
Great - hope so. In the meantime I have written a quick python code to pull the teams and sort them based on the current rules. I would love to change it into Jinja2 and keep everything in HA but I donāt Jinja conversion very well.
So the code is processing everything correctly but will wait to see what breaks. Pretty straight forward. Call the python code, writes the top 12 teams into a json file in the www directory, rest grabs the data from the file and puts it into a sensor and then I call the sensor.
Here is the screen shot:
Adding the python code here:
import json
import requests
# Define the API URL
api_url = "https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings?seasontype=2&type=0&level=3"
# Fetch the data from the API
response = requests.get(api_url)
data = response.json()
# Extract the relevant data
rankings = data['rankings'][0]
teams = rankings['ranks']
# Extract the relevant data
# teams = data['rankings']['ranks']
# Define the conference IDs
main_conferences = [1, 4, 5, 8]
other_conferences = [151, 12, 15, 17, 9, 37, 176, 20, 40, 22, 24, 25, 26, 27, 28, 29, 31, 30, 16]
# Process the rankings according to the CFP rules
def process_rankings(teams):
main_conference_teams = {1: None, 4: None, 5: None, 8: None}
other_conference_teams = []
remaining_teams = []
for team in teams:
conference_id = int(team['team']['groups']['id'])
if conference_id in main_conferences:
if main_conference_teams[conference_id] is None or team['current'] < main_conference_teams[conference_id]['current']:
main_conference_teams[conference_id] = team
elif conference_id in other_conferences:
other_conference_teams.append(team)
else:
remaining_teams.append(team)
# Filter out None values and sort the main conference teams by their current rank
top_main_conference_teams = sorted([team for team in main_conference_teams.values() if team], key=lambda x: x['current'])
# Ensure only one team from each main conference is in the top 4
top_4_teams = []
selected_conferences = set()
for team in top_main_conference_teams:
if team['team']['groups']['id'] not in selected_conferences:
top_4_teams.append(team)
selected_conferences.add(team['team']['groups']['id'])
if len(top_4_teams) == 4:
break
# Collect the remaining teams that weren't selected in the top 4
remaining_teams = [team for team in teams if team not in top_4_teams]
# Sort the remaining teams by their current rank
remaining_teams = sorted(remaining_teams, key=lambda x: x['current'])
# Select the next highest-ranked teams from the remaining teams to fill positions 5-12
next_highest_ranked_teams = []
for team in remaining_teams:
if team not in top_4_teams:
next_highest_ranked_teams.append(team)
if len(next_highest_ranked_teams) == 8:
break
# Select the lowest-seeded team from other conferences
other_conference_teams = sorted(other_conference_teams, key=lambda x: x['current'])
if other_conference_teams:
lowest_other_conference_team = other_conference_teams[0]
if 5 <= lowest_other_conference_team['current'] <= 12:
next_highest_ranked_teams.append(lowest_other_conference_team)
else:
# Replace the #12 team on the list
lowest_other_conference_team['current'] > 12
next_highest_ranked_teams[-1] = lowest_other_conference_team
# Combine the teams to form the final rankings
final_rankings = top_4_teams + next_highest_ranked_teams
# Add CFP_ranking to each team
for rank, team in enumerate(final_rankings, start=1):
team['CFP_ranking'] = rank
return final_rankings
# Get the final rankings
final_rankings = process_rankings(teams)
# Wrap the final rankings in a dictionary with the key 'teams'
output_data = {"teams": final_rankings}
# Print the final rankings
#print("Final Rankings:")
#for rank, team in enumerate(final_rankings, start=1):
# print(f"{rank}. {team['team']['nickname']} (CFP Ranking: {team['CFP_ranking']})")
# Save the final rankings to a JSON file
with open('cfp_final_rankings.json', 'w') as f:
json.dump(output_data, f, indent=4)
#print("Final rankings saved to final_rankings.json")
Here is the rest sensor:
- platform: rest
name: NCAAF CFP rankings
unique_id: sensor.ncaaf_cfp_rankings
resource: http://192.168.100.226:8123/local/cfp_final_rankings.json
value_template: "{{ now() }}"
json_attributes:
- teams
I havenāt put cards into a decluttering template yet, just trying to see if things work. Here is a quick snippet of what I am doing:
- square: false
columns: 4
type: grid
cards:
- type: custom:mushroom-template-card
entity: sensor.ncaaf_poll_cfp_top_25_all
primary: >
{% set rankings =
state_attr('sensor.ncaaf_cfp_rankings',
'teams') %} {% for ranking in rankings
%} {% if ranking.CFP_ranking == 1 %} 1.
{{ ranking.team.nickname }} {{
ranking.team.name }} {% endif %} {%
endfor %}
card_mod:
style: >
ha-card { background: url({% set
rankings =
state_attr('sensor.ncaaf_cfp_rankings',
'teams') %} {% for ranking in rankings
%} {% if ranking.CFP_ranking == 1 %} {{
ranking.team.logos[0].href }} {% endif
%} {% endfor %}) no-repeat right center;
background-size: contain; /* Adjust this
value to scale the image */ color:
white; /* Adjust text color for better
visibility */ border: 2px solid {% set
rankings =
state_attr('sensor.ncaaf_cfp_rankings',
'teams') %} {% for ranking in rankings
%} {% if ranking.CFP_ranking == 1 %} #{{
ranking.team.color }} {% endif %} {%
endfor %}; border-radius: 10px; /*
Optional: Add rounded corners */
padding: 10px; /* Optional: Add padding
inside the card */ }
As always hope this helps and looking for anything that improves what I am doing
Hey bro @bburwell, where do you get this URL? Iām trying to see the starting goalies info, but I donĀ“t know where to get that URL
Using multiscrape:
- name: Goalie scraper
resource: https://www.dailyfaceoff.com/starting-goalies
scan_interval: 43200
sensor:
- unique_id: nhl_starting_goalies
name: NHL Starting Goalies
select: '#__NEXT_DATA__'
value_template: '{{ now() }}'
attributes:
- name: data
select: '#__NEXT_DATA__'
value_template: >
{{ (value | from_json)['props']['pageProps']['data'] }}
So for anyone that used my python code for the CFP pull it was incorrect. I am hoping that this fixes it (seems to be correct) and will continue to refine. I am working on bowl games as well but I might need a Ouija board for that and honestly I am hoping that ESPN will add it to the api.
Here is the updated python code:
import json
import requests
# Define the API URL
api_url = "https://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings?seasontype=2&type=0&level=3"
# Fetch the data from the API
response = requests.get(api_url)
data = response.json()
# Extract the relevant data
rankings = data['rankings'][0]
teams = rankings['ranks']
# Define the conference IDs
main_conferences = [1, 4, 5, 8, 12, 15, 17, 37, 151]
# Process the rankings according to the CFP rules
def process_rankings(teams):
main_conference_teams = {1: None, 4: None, 5: None, 8: None, 12: None, 15: None, 17: None, 37: None, 151: None}
remaining_teams = []
for team in teams:
conference_id = int(team['team']['groups']['id'])
if conference_id in main_conferences:
if main_conference_teams[conference_id] is None or team['current'] < main_conference_teams[conference_id]['current']:
main_conference_teams[conference_id] = team
else:
remaining_teams.append(team)
# Filter out None values and sort the main conference teams by their current rank
top_main_conference_teams = sorted([team for team in main_conference_teams.values() if team], key=lambda x: x['current'])
# Ensure only one team from each main conference is in the top 4
top_4_teams = []
selected_conferences = set()
for team in top_main_conference_teams:
if team['team']['groups']['id'] not in selected_conferences:
top_4_teams.append(team)
selected_conferences.add(team['team']['groups']['id'])
if len(top_4_teams) == 4:
break
# Collect the remaining teams that weren't selected in the top 4
remaining_teams = [team for team in teams if team not in top_4_teams]
# Sort the remaining teams by their current rank
remaining_teams = sorted(remaining_teams, key=lambda x: x['current'])
# Select the next highest-ranked teams from the remaining teams to fill positions 5-12
next_highest_ranked_teams = []
for team in remaining_teams:
if team not in top_4_teams:
next_highest_ranked_teams.append(team)
if len(next_highest_ranked_teams) == 8:
break
# Combine the teams to form the final rankings
final_rankings = top_4_teams + next_highest_ranked_teams
# Adjust the 5th highest-ranked conference winner if necessary
if len(top_main_conference_teams) > 4:
fifth_highest_conference_team = top_main_conference_teams[4]
if fifth_highest_conference_team['current'] > 12:
final_rankings = final_rankings[:-1] + [fifth_highest_conference_team]
# Add CFP_ranking to each team
for rank, team in enumerate(final_rankings, start=1):
team['CFP_ranking'] = rank
return final_rankings
# Get the final rankings
final_rankings = process_rankings(teams)
# Wrap the final rankings in a dictionary with the key 'teams'
output_data = {"teams": final_rankings}
# Save the final rankings to a JSON file
with open('/config/www/cfp_final_rankings.json', 'w') as f:
json.dump(output_data, f, indent=4)
I just use a shell command to make the call Tuesday around 5pm EST.
shell_command:
get_cfp: 'python /config/www/cfppull.py'
I will update the layout (pretty it up) later and will fix the top 25 colors but it gives you an idea of where I am heading.
First of all, thank you for this. This is amazing. Iām trying to get this up an running for myself, please forgive the lack of HA and coding knowledge, Iām trying to learn as I go. Hereās my current configuration.yaml:
template:
- sensor:
- name: "Visibility in Miles"
unit_of_measurement: "mi"
state: >
{{ (states('sensor.openweathermap_visibility') | float / 1609.34) | round(1) }}
# Adding NHL Sensors
- sensor:
- platform: rest
scan_interval: 3600
name: NHL Standings
unique_id: sensor.nhl_standings
resource: https://site.web.api.espn.com/apis/v2/sports/hockey/nhl/standings?seasontype=2&type=0&level=3
value_template: "{{ now() }}"
json_attributes:
- children
- platform: rest
scan_interval: 3600
name: NHL Scores
unique_id: sensor.nhl_scores
resource: https://site.api.espn.com/apis/v2/scoreboard/header?sport=hockey&league=nhl
value_template: "{{ now() }}"
json_attributes:
- sports
## NHL Teams
##
- platform: teamtracker
league_id: NHL
team_id: DET
name: Detroit Red Wings
- platform: teamtracker
league_id: NHL
team_id: NSH
name: Nashville Predators
- platform: teamtracker
league_id: NHL
team_id: FLA
name: Florida Panthers
- platform: teamtracker
league_id: NHL
team_id: STL
name: St Louis Blues
- platform: teamtracker
league_id: NHL
team_id: COL
name: Colorado Avalanche
- platform: teamtracker
league_id: NHL
team_id: BOS
name: Boston Bruins
- platform: teamtracker
league_id: NHL
team_id: NYI
name: New York Islanders
- platform: teamtracker
league_id: NHL
team_id: PIT
name: Pittsburg Penguins
- platform: teamtracker
league_id: NHL
team_id: NJ
name: New Jersey Devils
- platform: teamtracker
league_id: NHL
team_id: DAL
name: Dallas Stars
- platform: teamtracker
league_id: NHL
team_id: CBJ
name: Columbus Blue Jackets
- platform: teamtracker
league_id: NHL
team_id: TOR
name: Toronto Maple Leafs
- platform: teamtracker
league_id: NHL
team_id: MTL
name: Montreal Canadians
- platform: teamtracker
league_id: NHL
team_id: CAR
name: Carolina Hurricanes
- platform: teamtracker
league_id: NHL
team_id: WSH
name: Washington Senators
- platform: teamtracker
league_id: NHL
team_id: CGY
name: Calgary Flames
- platform: teamtracker
league_id: NHL
team_id: NYR
name: New York Rangers
- platform: teamtracker
league_id: NHL
team_id: VAN
name: Vancouver Canucks
- platform: teamtracker
league_id: NHL
team_id: PHI
name: Philadelphia Fylers
- platform: teamtracker
league_id: NHL
team_id: LA
name: Los Angeles Kings
- platform: teamtracker
league_id: NHL
team_id: ARI
name: Arizona Coyotes
- platform: teamtracker
league_id: NHL
team_id: SJ
name: San Jose Sharks
- platform: teamtracker
league_id: NHL
team_id: BUF
name: Buffalo Sabres
- platform: teamtracker
league_id: NHL
team_id: SEA
name: Seattle Kraken
- platform: teamtracker
league_id: NHL
team_id: VGK
name: Los Vegas Golden Knights
- platform: teamtracker
league_id: NHL
team_id: TB
name: Tampa Bay Lightning
- platform: teamtracker
league_id: NHL
team_id: OTT
name: Ottawa Senators
- platform: teamtracker
league_id: NHL
team_id: WPG
name: Winnipeg Jets
- platform: teamtracker
league_id: NHL
team_id: EDM
name: Edmonton Oilers
- platform: teamtracker
league_id: NHL
team_id: MIN
name: Minnesota Wild
- platform: teamtracker
league_id: NHL
team_id: ANA
name: Anaheim Ducks
- platform: teamtracker
league_id: NHL
team_id: CHI
name: Chicago Blackhawks
### NHL Divisions
- name: NHL East Atlantic
unique_id: sensor.nhl_east_atlantic
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[0]['children'][0]['standings']['entries'] }}"
- name: NHL East Metropolitan
unique_id: sensor.nhl_east_metropolitan
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[0]['children'][1]['standings']['entries'] }}"
- name: NHL West Central
unique_id: sensor.nhl_west_central
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[1]['children'][0]['standings']['entries'] }}"
- name: NHL West Pacific
unique_id: sensor.nhl_west_pacific
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[1]['children'][1]['standings']['entries'] }}"
### NHL Wildcard
###
- name: NHL Wildcard Standings
unique_id: sensor.nhl_wildcard_standings
state: "{{ now() }}"
attributes:
east_atlantic_top: "{{ state_attr('sensor.nhl_wildcard','overall')[0]['children'][0]['standings']['entries'] }}"
east_metropolitan_top: "{{ state_attr('sensor.nhl_wildcard','overall')[0]['children'][1]['standings']['entries'] }}"
east_wildcard: "{{ state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][:2] }}"
east_hunt: >
{% set hteams = namespace(hteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][2:] %}
{% for stat in team['stats'] | selectattr('name','eq','clincher') %}
{% else %}
{% set hteams.hteam = hteams.hteam + [team] %}
{% endfor %}
{% endfor %}
{{ hteams.hteam }}
east_eliminated: >
{% set eteams = namespace(eteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][2:] %}
{% for stat in team['stats'] %}
{% if stat.name == 'clincher' %}
{% set eteams.eteam = eteams.eteam + [team] %}
{% endif %}
{% endfor %}
{% endfor %}
{{ eteams.eteam }}
west_central_top: "{{ state_attr('sensor.nhl_wildcard','overall')[1]['children'][0]['standings']['entries'] }}"
west_pacific_top: "{{ state_attr('sensor.nhl_wildcard','overall')[1]['children'][1]['standings']['entries'] }}"
west_wildcard: "{{ state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][:2] }}"
west_hunt: >
{% set hteams = namespace(hteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][2:] %}
{% for stat in team['stats'] | selectattr('name','eq','clincher') %}
{% else %}
{% set hteams.hteam = hteams.hteam + [team] %}
{% endfor %}
{% endfor %}
{{ hteams.hteam }}
west_eliminated: >
{% set eteams = namespace(eteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][2:] %}
{% for stat in team['stats'] %}
{% if stat.name == 'clincher' %}
{% set eteams.eteam = eteams.eteam + [team] %}
{% endif %}
{% endfor %}
{% endfor %}
{{ eteams.eteam }}
And the errors that HA is throwing:
Logger: homeassistant.helpers.event
Source: helpers/template.py:635
First occurred: 9:41:48 PM (2 occurrences)
Last logged: 9:41:52 PM
Error while processing template: Template<template=({%- for team in integration_entities("teamtracker") -%} {%- if state_attr(team, "league") == "NHL" -%} {%- if states(team) == "PRE" -%} {%- 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 -%}) renders=2>
Error while processing template: Template<template=({%- for team in integration_entities("teamtracker") -%} {%- if state_attr(team, "league") == "NHL" -%} {%- if states(team) == "PRE" -%} {%- 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 -%}) renders=6>
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 633, in async_render
render_result = _render_with_context(self.template, compiled, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 2735, in _render_with_context
return template.render(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/jinja2/environment.py", line 1304, in render
self.environment.handle_exception()
File "/usr/local/lib/python3.12/site-packages/jinja2/environment.py", line 939, in handle_exception
raise rewrite_traceback_stack(source=source)
File "<template>", line 5, in top-level template code
TypeError: argument of type 'NoneType' is not iterable
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 750, in async_render_to_info
render_info._result = self.async_render( # noqa: SLF001
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 635, in async_render
raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: TypeError: argument of type 'NoneType' is not iterable
And
Logger: homeassistant.config
Source: config.py:357
First occurred: 9:41:24 PM (1 occurrences)
Last logged: 9:41:24 PM
Invalid config for 'template' at configuration.yaml, line 31: 'platform' is an invalid option for 'template', check: sensor->0->platform Invalid config for 'template' at configuration.yaml, line 31: required key 'state' not provided Invalid config for 'template' at configuration.yaml, line 32: 'scan_interval' is an invalid option for 'template', check: sensor->0->scan_interval Invalid config for 'template' at configuration.yaml, line 35: 'resource' is an invalid option for 'template', check: sensor->0->resource Invalid config for 'template' at configuration.yaml, line 36: 'value_template' is an invalid option for 'template', check: sensor->0->value_template Invalid config for 'template' at configuration.yaml, line 37: 'json_attributes' is an invalid option for 'template', check: sensor->0->json_attributes
What am I missing or doing wrong? Any and all help is greatly appreciated.
Your errors are because the format for the data in configuration.yaml isnāt correct.
Iāll be honest I wouldnāt encourage you to put everything in the configuration.yaml. It will become a beast to manage if you do what some of us do and chase sports down the rabbit hole.
Others I am sure are different but in my configuration I separate the sensors out by sport. I bunch Template into a single file but I have moved almost everything to sensors.
Here is what I have in my configuration.yaml and the includes are pointing to directories I created in the config directory.
group: !include groups.yaml
automation: !include automations.yaml
script: !include scripts.yaml
sensor: !include_dir_merge_list sensors/
template: !include template.yaml
You can see some of the different files here:
In NHL starting at the top this is how my file looks:
The top of my template file looks like this:
I would pop-over to @kbrown01 github here and copy what he has done
https://github.com/kbrown01/SportStandingsScores
Appreciate you. Iām only looking to do only NHL stats. So, let me go a little further back. Running HA in virttualbox. So Iām going to need to create separate yaml files using file editor in the same location as my config.yaml? I like your organization, so I want to create a yaml for nhl sensors, sensors, groups and templates correct?
So basically adjust the sensor.yaml and the template yaml from the git directory and add those to the files I created? Then in my config.yaml, point it to the directories I created?
Or am i just overthinking this and there is an easier way to do it?
@kbrown01 github has the yamlās you need - sensor, template and dashboard. You could just pull out the NHL stuff for Sensor/Template and then grab his code for an NHL dashboard in his dashboard.yaml.
ok I think i got that
sensor.yaml
# Adding NHL Sensors
- sensor:
- platform: rest
scan_interval: 3600
name: NHL Standings
unique_id: sensor.nhl_standings
resource: https://site.web.api.espn.com/apis/v2/sports/hockey/nhl/standings?seasontype=2&type=0&level=3
value_template: "{{ now() }}"
json_attributes:
- children
- platform: rest
scan_interval: 3600
name: NHL Scores
unique_id: sensor.nhl_scores
resource: https://site.api.espn.com/apis/v2/scoreboard/header?sport=hockey&league=nhl
value_template: "{{ now() }}"
json_attributes:
- sports
## NHL Teams
##
- platform: teamtracker
league_id: NHL
team_id: DET
name: Detroit Red Wings
- platform: teamtracker
league_id: NHL
team_id: NSH
name: Nashville Predators
- platform: teamtracker
league_id: NHL
team_id: FLA
name: Florida Panthers
- platform: teamtracker
league_id: NHL
team_id: STL
name: St Louis Blues
- platform: teamtracker
league_id: NHL
team_id: COL
name: Colorado Avalanche
- platform: teamtracker
league_id: NHL
team_id: BOS
name: Boston Bruins
- platform: teamtracker
league_id: NHL
team_id: NYI
name: New York Islanders
- platform: teamtracker
league_id: NHL
team_id: PIT
name: Pittsburg Penguins
- platform: teamtracker
league_id: NHL
team_id: NJ
name: New Jersey Devils
- platform: teamtracker
league_id: NHL
team_id: DAL
name: Dallas Stars
- platform: teamtracker
league_id: NHL
team_id: CBJ
name: Columbus Blue Jackets
- platform: teamtracker
league_id: NHL
team_id: TOR
name: Toronto Maple Leafs
- platform: teamtracker
league_id: NHL
team_id: MTL
name: Montreal Canadians
- platform: teamtracker
league_id: NHL
team_id: CAR
name: Carolina Hurricanes
- platform: teamtracker
league_id: NHL
team_id: WSH
name: Washington Senators
- platform: teamtracker
league_id: NHL
team_id: CGY
name: Calgary Flames
- platform: teamtracker
league_id: NHL
team_id: NYR
name: New York Rangers
- platform: teamtracker
league_id: NHL
team_id: VAN
name: Vancouver Canucks
- platform: teamtracker
league_id: NHL
team_id: PHI
name: Philadelphia Fylers
- platform: teamtracker
league_id: NHL
team_id: LA
name: Los Angeles Kings
- platform: teamtracker
league_id: NHL
team_id: ARI
name: Arizona Coyotes
- platform: teamtracker
league_id: NHL
team_id: SJ
name: San Jose Sharks
- platform: teamtracker
league_id: NHL
team_id: BUF
name: Buffalo Sabres
- platform: teamtracker
league_id: NHL
team_id: SEA
name: Seattle Kraken
- platform: teamtracker
league_id: NHL
team_id: VGK
name: Los Vegas Golden Knights
- platform: teamtracker
league_id: NHL
team_id: TB
name: Tampa Bay Lightning
- platform: teamtracker
league_id: NHL
team_id: OTT
name: Ottawa Senators
- platform: teamtracker
league_id: NHL
team_id: WPG
name: Winnipeg Jets
- platform: teamtracker
league_id: NHL
team_id: EDM
name: Edmonton Oilers
- platform: teamtracker
league_id: NHL
team_id: MIN
name: Minnesota Wild
- platform: teamtracker
league_id: NHL
team_id: ANA
name: Anaheim Ducks
- platform: teamtracker
league_id: NHL
team_id: CHI
name: Chicago Blackhawks
template.yaml
sensor:
### NHL Divisions
###
- name: NHL East Atlantic
unique_id: sensor.nhl_east_atlantic
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[0]['children'][0]['standings']['entries'] }}"
- name: NHL East Metropolitan
unique_id: sensor.nhl_east_metropolitan
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[0]['children'][1]['standings']['entries'] }}"
- name: NHL West Central
unique_id: sensor.nhl_west_central
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[1]['children'][0]['standings']['entries'] }}"
- name: NHL West Pacific
unique_id: sensor.nhl_west_pacific
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[1]['children'][1]['standings']['entries'] }}"
###
### NHL Wildcard
###
- name: NHL Wildcard Standings
unique_id: sensor.nhl_wildcard_standings
state: "{{ now() }}"
attributes:
east_atlantic_top: "{{ state_attr('sensor.nhl_wildcard','overall')[0]['children'][0]['standings']['entries'] }}"
east_metropolitan_top: "{{ state_attr('sensor.nhl_wildcard','overall')[0]['children'][1]['standings']['entries'] }}"
east_wildcard: "{{ state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][:2] }}"
east_hunt: >
{% set hteams = namespace(hteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][2:] %}
{% for stat in team['stats'] | selectattr('name','eq','clincher') %}
{% else %}
{% set hteams.hteam = hteams.hteam + [team] %}
{% endfor %}
{% endfor %}
{{ hteams.hteam }}
east_eliminated: >
{% set eteams = namespace(eteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][2:] %}
{% for stat in team['stats'] %}
{% if stat.name == 'clincher' %}
{% set eteams.eteam = eteams.eteam + [team] %}
{% endif %}
{% endfor %}
{% endfor %}
{{ eteams.eteam }}
west_central_top: "{{ state_attr('sensor.nhl_wildcard','overall')[1]['children'][0]['standings']['entries'] }}"
west_pacific_top: "{{ state_attr('sensor.nhl_wildcard','overall')[1]['children'][1]['standings']['entries'] }}"
west_wildcard: "{{ state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][:2] }}"
west_hunt: >
{% set hteams = namespace(hteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][2:] %}
{% for stat in team['stats'] | selectattr('name','eq','clincher') %}
{% else %}
{% set hteams.hteam = hteams.hteam + [team] %}
{% endfor %}
{% endfor %}
{{ hteams.hteam }}
west_eliminated: >
{% set eteams = namespace(eteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][2:] %}
{% for stat in team['stats'] %}
{% if stat.name == 'clincher' %}
{% set eteams.eteam = eteams.eteam + [team] %}
{% endif %}
{% endfor %}
{% endfor %}
{{ eteams.eteam }}
conāt
dashboard adjusted to just NHL:
decluttering_templates:
nhl_settings:
card:
type: custom:flex-table-card
title: '[[title]]'
css:
table+: 'padding: 0px; width: 1600px;'
tbody tr td:first-child: 'width: 2%;'
tbody tr td:nth-child(2): 'width: 20%;'
tbody tr td:nth-child(n+3): 'width: 5%;'
tbody tr:hover: 'background-color: green!important; color:white!important;'
tbody tr td:nth-child(7): '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]]'
exclude: '[[excluded_entities]]'
sort_by: entries-
columns:
- hidden: true
data: '[[attribute]]'
modify: '[[sort]]'
- name: <div>C</div>
data: '[[attribute]]'
modify: >-
if(typeof x.stats.find(y=>y.abbreviation == 'CLINCH') !==
'undefined' ){x.stats.find(y=>y.abbreviation ==
'CLINCH').displayValue}else{'-'}
- name: Team
data: '[[attribute]]'
modify: >-
'<div><a href="' + x.team.links[0].href + '" target="_blank"><img
src="' + x.team.logos[0].href + '" style="height:
20px;vertical-align:middle;"></a> ' + x.team.displayName +
'</div>'
- name: <div>GP</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'GP').displayValue
- name: <div>W</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'W').displayValue
- name: <div>L</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'L').displayValue
- name: <div>OTL</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'OTL').displayValue
- name: <div>PTS</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'PTS').displayValue
- name: <div>RW</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'RW').displayValue
- name: <div>ROW</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'ROW').displayValue
- name: <div>SOW</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'SOW').displayValue
- name: <div>SOL</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'SOL').displayValue
- name: <div>HOME</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'HOME').displayValue
- name: <div>AWAY</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'AWAY').displayValue
- name: <div>GF</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'GF').displayValue
- name: <div>GA</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'GA').displayValue
- name: <div>DIFF</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'DIFF').displayValue
- name: <div>L10</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'L10').summary
- name: <div>STRK</div>
data: '[[attribute]]'
modify: x.stats.find(y=>y.abbreviation == 'STRK').displayValue
game_stats:
card:
type: custom:auto-entities
unique: true
show_empty: false
card:
type: custom:layout-card
layout_type: masonry
width: 200px
max-columns: 5
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
views:
- theme: Backend-selected
title: Sports
type: panel
icon: mdi:strategy
badges: []
cards:
- type: custom:mod-card
card_mod:
style:
tabbed-card $: |
mwc-tab {
background: var(--ha-card-background, var(--card-background-color, white) );
border-color: var(--ha-card-border-color, var(--divider-color, #e0e0e0) );
border-width: 2px;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
border-style: solid;
overflow: hidden;
width: 25%;
}
mwc-tab[active] {
background: #EBFFD8 !important;
}
card:
type: custom:tabbed-card
styles:
'--mdc-theme-primary': green
'--mdc-tab-text-label-color-default': silver
'--mdc-typography-button-font-size': 12px
tabs:
- attributes:
label: NHL
icon: mdi:hockey-puck
card:
type: custom:mod-card
card_mod:
style:
tabbed-card $: |
mwc-tab {
background: var(--ha-card-background, var(--card-background-color, white) );
border-color: var(--ha-card-border-color, var(--divider-color, #e0e0e0) );
border-width: 2px;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
border-style: solid;
overflow: hidden;
width: 20%;
}
mwc-tab[active] {
background: #EBFFD8 !important;
}
card:
type: custom:tabbed-card
styles:
'--mdc-theme-primary': green
'--mdc-tab-text-label-color-default': silver
'--mdc-typography-button-font-size': 12px
tabs:
- attributes:
label: Standings
icon: mdi:ballot
card:
type: custom:mod-card
card_mod:
style:
tabbed-card $: |
mwc-tab {
background: var(--ha-card-background, var(--card-background-color, white) );
border-color: var(--ha-card-border-color, var(--divider-color, #e0e0e0) );
border-width: 2px;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
border-style: solid;
overflow: hidden;
width: 25%;
}
mwc-tab[active] {
background: #EBFFD8 !important;
}
card:
type: custom:tabbed-card
styles:
'--mdc-theme-primary': green
'--mdc-tab-text-label-color-default': silver
'--mdc-typography-button-font-size': 12px
tabs:
- attributes:
label: Divisional
card:
type: custom:stack-in-card
mode: vertical
cards:
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Eastern Atlantic
- entity: sensor.nhl_east_atlantic
- attribute: entries
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Eastern Metropolitan
- entity: sensor.nhl_east_metropolitan
- attribute: entries
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: West Central
- entity: sensor.nhl_west_central
- attribute: entries
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: West Pacific
- entity: sensor.nhl_west_pacific
- attribute: entries
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- attributes:
label: Conference
card:
type: custom:stack-in-card
mode: vertical
cards:
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Eastern
- entity: sensor.nhl_east_*
- attribute: entries
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Western
- entity: sensor.nhl_west_*
- attribute: entries
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- attributes:
label: Overall
card:
type: custom:decluttering-card
template: nhl_settings
variables:
- title: Overall
- entity: sensor.nhl_*_*
- attribute: entries
- excluded_entities:
- sensor.nhl_starting_goalies
- sensor.nhl_wildcard
- sensor.nhl_wildcard_standings
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- attributes:
label: Wildcard
card:
type: custom:stack-in-card
mode: vertical
cards:
- type: markdown
content: |
<h2>Eastern Conference</h2>
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Atlantic Leaders
- entity: sensor.nhl_wildcard_standings
- attribute: east_atlantic_top
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Metropolitan Leaders
- entity: sensor.nhl_wildcard_standings
- attribute: east_metropolitan_top
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Wildcards
- entity: sensor.nhl_wildcard_standings
- attribute: east_wildcard
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: In The Hunt
- entity: sensor.nhl_wildcard_standings
- attribute: east_hunt
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Eliminated
- entity: sensor.nhl_wildcard_standings
- attribute: east_eliminated
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: markdown
content: |
<h2>Western Conference</h2>
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Central Leaders
- entity: sensor.nhl_wildcard_standings
- attribute: west_central_top
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Pacific Leaders
- entity: sensor.nhl_wildcard_standings
- attribute: west_pacific_top
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Wildcards
- entity: sensor.nhl_wildcard_standings
- attribute: west_wildcard
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: In The Hunt
- entity: sensor.nhl_wildcard_standings
- attribute: west_hunt
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- type: custom:decluttering-card
template: nhl_settings
variables:
- title: Eliminated
- entity: sensor.nhl_wildcard_standings
- attribute: west_eliminated
- excluded_entities:
- sensor.nhl_starting_goalies
- sort: >-
x.stats.find(y=>y.shortDisplayName ==
'PTS').value
- attributes:
label: Postgame
icon: mdi:hockey-sticks
card:
type: custom:decluttering-card
template: game_stats
variables:
- sport: NHL
- status: POST
- attributes:
label: Live
icon: mdi:hockey-puck
card:
type: custom:decluttering-card
template: game_stats
variables:
- sport: NHL
- status: IN
- attributes:
label: Pregame
icon: mdi:blood-bag
card:
type: custom:decluttering-card
template: game_stats
variables:
- sport: NHL
- status: PRE
- attributes:
label: Starting Goalies
icon: mdi:strategy
card:
type: custom:flex-table-card
title: Starting Goalies
css:
table+: 'padding: 0px; width: 1600px;'
tbody tr:hover: >-
background-color: green!important;
color:white!important;
tbody tr td:nth-child(10): 'background-color: green; color: white;'
card_mod:
style: |
ha-card {
overflow: auto;
}
$: |
.card-header {
padding: 12px 0px 8px 4px!important;
font-size: 16px!important;
line-height: 18px!important;
font-weight: bold!important;
}
entities:
include: sensor.nhl_starting_goalies
columns:
- name: HOME
data: data
modify: >-
'<div><img src="' + x.homeTeamLogoSvg + '"
style="height:
20px;vertical-align:middle;"> ' +
x.homeTeamName + '</div>'
- name: GOALIE
data: data
modify: x.homeGoalieName
- name: RANK
data: data
modify: >-
if(x.homeGoaliePositionRank === null
){'None'}else{x.homeGoaliePositionRank}
- name: W
data: data
modify: >-
if(x.homeGoalieWins === null
){'0'}else{x.homeGoalieWins}
- name: L
data: data
modify: >-
if(x.homeGoalieLosses === null
){'0'}else{x.homeGoalieLosses}
- name: OTL
data: data
modify: >-
if(x.homeGoalieOvertimeLosses === null
){'0'}else{x.homeGoalieOvertimeLosses}
- name: SO
data: data
modify: >-
if(x.homeGoalieShutouts === null
){'0'}else{x.homeGoalieShutouts}
- name: SVP
data: data
modify: Number(x.homeGoalieSavePercentage).toFixed(3)
- name: GAA
data: data
modify: Number(x.homeGoalieGoalsAgainstAvg).toFixed(2)
- name: PICK
data: data
modify: |-
switch(true){
case (x.homeGoalieGoalsAgainstAvg == 0) || (x.awayGoalieGoalsAgainstAvg == 0) :
'<div style="text-align:center;"><ha-icon icon="mdi:crosshairs-question"></div>';
break;
case (x.homeGoalieGoalsAgainstAvg - x.awayGoalieGoalsAgainstAvg) > 0:
'<div style="text-align:center;"><ha-icon icon="mdi:arrow-right"></div>';
break;
case (x.homeGoalieGoalsAgainstAvg - x.awayGoalieGoalsAgainstAvg) < 0:
'<div style="text-align:center;"><ha-icon icon="mdi:arrow-left"></div>';
break;
default:
'<div style="text-align:center;"><ha-icon icon="mdi:arrow-all"></div>';
}
- name: AWAY
data: data
modify: >-
'<div><img src="' + x.awayTeamLogoSvg + '"
style="height:
20px;vertical-align:middle;"> ' +
x.awayTeamName + '</div>'
- name: GOALIE
data: data
modify: x.awayGoalieName
- name: RANK
data: data
modify: >-
if(x.awayGoaliePositionRank == null
){'None'}else{x.awayGoaliePositionRank}
- name: W
data: data
modify: >-
if(x.awayGoalieWins === null
){'0'}else{x.awayGoalieWins}
- name: L
data: data
modify: >-
if(x.awayGoalieLosses === null
){'0'}else{x.awayGoalieLosses}
- name: OTL
data: data
modify: >-
if(x.awayGoalieOvertimeLosses === null
){'0'}else{x.awayGoalieOvertimeLosses}
- name: SO
data: data
modify: >-
if(x.awayGoalieShutouts === null
){'0'}else{x.awayGoalieShutouts}
- name: SVP
data: data
modify: Number(x.awayGoalieSavePercentage).toFixed(3)
- name: GAA
data: data
modify: Number(x.awayGoalieGoalsAgainstAvg).toFixed(2)
title: Sports Standings and Scores
which gets me what I wantā¦just now info. I have to hammer down the config directory navigation and figure out how to get the info loading. But it looks like this;
so progress lol
A couple points:
- shouldnāt need that sensor: in sensor.yaml file as long as you are pointing to it in your configuration.yaml. Not sure HA will run without but honestly donāt know.
Also look at how @kbrown01 has the yamlās formatted - they are to the left. Copy his code exactly.
Coding is very finicky about alignment, spacing, etc. I am always checking 2 places: log files for errors (search on NHL in your case) and the sensor themselves. If they are empty it is either a code issue or possibly an api pull issue.
@ehcah taught me this trick - uncheck the attributes box after your first load - makes searching much faster
When it is working You should see data in the sensor
will play around with it and report backā¦thanks brotha
ok, having issues with the template. Copied and pasted directly from the git file. HA is throwing these errorsā¦thoughts?
Logger: homeassistant.helpers.event
Source: helpers/template.py:635
First occurred: 1:32:06 PM (2 occurrences)
Last logged: 1:32:06 PM
Error while processing template: Template<template=({%- for team in integration_entities("teamtracker") -%} {%- if state_attr(team, "league") == "NHL" -%} {%- if states(team) == "POST" -%} {%- 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 -%}) renders=2>
Error while processing template: Template<template=({%- for team in integration_entities("teamtracker") -%} {%- if state_attr(team, "league") == "NHL" -%} {%- if states(team) == "PRE" -%} {%- 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 -%}) renders=2>
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 633, in async_render
render_result = _render_with_context(self.template, compiled, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 2735, in _render_with_context
return template.render(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/jinja2/environment.py", line 1304, in render
self.environment.handle_exception()
File "/usr/local/lib/python3.12/site-packages/jinja2/environment.py", line 939, in handle_exception
raise rewrite_traceback_stack(source=source)
File "<template>", line 5, in top-level template code
TypeError: argument of type 'NoneType' is not iterable
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 750, in async_render_to_info
render_info._result = self.async_render( # noqa: SLF001
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 635, in async_render
raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: TypeError: argument of type 'NoneType' is not iterable
Logger: homeassistant.config
Source: config.py:357
First occurred: 1:32:05 PM (5 occurrences)
Last logged: 1:32:05 PM
Invalid config for 'template' at template.yaml, line 4: 'name' is an invalid option for 'template', check: name Invalid config for 'template' at template.yaml, line 6: 'state' is an invalid option for 'template', check: state Invalid config for 'template' at template.yaml, line 7: 'attributes' is an invalid option for 'template', check: attributes
Invalid config for 'template' at template.yaml, line 11: 'state' is an invalid option for 'template', check: state Invalid config for 'template' at template.yaml, line 12: 'attributes' is an invalid option for 'template', check: attributes Invalid config for 'template' at template.yaml, line 9: 'name' is an invalid option for 'template', check: name
Invalid config for 'template' at template.yaml, line 14: 'name' is an invalid option for 'template', check: name Invalid config for 'template' at template.yaml, line 16: 'state' is an invalid option for 'template', check: state Invalid config for 'template' at template.yaml, line 17: 'attributes' is an invalid option for 'template', check: attributes
Invalid config for 'template' at template.yaml, line 19: 'name' is an invalid option for 'template', check: name Invalid config for 'template' at template.yaml, line 21: 'state' is an invalid option for 'template', check: state Invalid config for 'template' at template.yaml, line 22: 'attributes' is an invalid option for 'template', check: attributes
Invalid config for 'template' at template.yaml, line 27: 'name' is an invalid option for 'template', check: name Invalid config for 'template' at template.yaml, line 29: 'state' is an invalid option for 'template', check: state Invalid config for 'template' at template.yaml, line 30: 'attributes' is an invalid option for 'template', check: attributes
Both standing and wildcard now have info on the sensorā¦so again, progress
what do the first 30 lines of your template.yaml file look like? Is your template file identical to @kbrown01 template.yaml? Alignment and text?
###
### NHL Divisions
###
- name: NHL East Atlantic
unique_id: sensor.nhl_east_atlantic
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[0]['children'][0]['standings']['entries'] }}"
- name: NHL East Metropolitan
unique_id: sensor.nhl_east_metropolitan
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[0]['children'][1]['standings']['entries'] }}"
- name: NHL West Central
unique_id: sensor.nhl_west_central
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[1]['children'][0]['standings']['entries'] }}"
- name: NHL West Pacific
unique_id: sensor.nhl_west_pacific
state: "{{ now() }}"
attributes:
entries: "{{ state_attr('sensor.nhl_standings','children')[1]['children'][1]['standings']['entries'] }}"
###
### NHL Wildcard
###
- name: NHL Wildcard Standings
unique_id: sensor.nhl_wildcard_standings
state: "{{ now() }}"
attributes:
east_atlantic_top: "{{ state_attr('sensor.nhl_wildcard','overall')[0]['children'][0]['standings']['entries'] }}"
east_metropolitan_top: "{{ state_attr('sensor.nhl_wildcard','overall')[0]['children'][1]['standings']['entries'] }}"
east_wildcard: "{{ state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][:2] }}"
east_hunt: >
{% set hteams = namespace(hteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][2:] %}
{% for stat in team['stats'] | selectattr('name','eq','clincher') %}
{% else %}
{% set hteams.hteam = hteams.hteam + [team] %}
{% endfor %}
{% endfor %}
{{ hteams.hteam }}
east_eliminated: >
{% set eteams = namespace(eteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[0]['standings']['entries'][2:] %}
{% for stat in team['stats'] %}
{% if stat.name == 'clincher' %}
{% set eteams.eteam = eteams.eteam + [team] %}
{% endif %}
{% endfor %}
{% endfor %}
{{ eteams.eteam }}
west_central_top: "{{ state_attr('sensor.nhl_wildcard','overall')[1]['children'][0]['standings']['entries'] }}"
west_pacific_top: "{{ state_attr('sensor.nhl_wildcard','overall')[1]['children'][1]['standings']['entries'] }}"
west_wildcard: "{{ state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][:2] }}"
west_hunt: >
{% set hteams = namespace(hteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][2:] %}
{% for stat in team['stats'] | selectattr('name','eq','clincher') %}
{% else %}
{% set hteams.hteam = hteams.hteam + [team] %}
{% endfor %}
{% endfor %}
{{ hteams.hteam }}
west_eliminated: >
{% set eteams = namespace(eteam=[]) %}
{% for team in state_attr('sensor.nhl_wildcard','children')[1]['standings']['entries'][2:] %}
{% for stat in team['stats'] %}
{% if stat.name == 'clincher' %}
{% set eteams.eteam = eteams.eteam + [team] %}
{% endif %}
{% endfor %}
{% endfor %}
{{ eteams.eteam }}
copy and pasted directly from the git fileā¦nothing above the ā###ā