NFL game sensor (scores, possession, etc)

yep, that should do the trick! I’m not too well versed with jinja2 / HA templates. If you wanted them to be ready out of the box, it would be nice to prepend the hex values with #

edit: oh btw, for possession to be useful, you’ll also need team id.
{{ competitor.team.id }}

edit2: added a PR for those values.

1 Like

awesome job guys

If Anyone can / wants to take this and include it in the rest sensors ( i dont really understand rest template like you guys do), i added espn news headlines for a particular team. Was only able to do this in node red . Edit: the website to fetch is http://site.api.espn.com/apis/site/v2/sports/football/nfl/news


Imgur

card:

type: vertical-stack
cards:
  - type: markdown
    content: >
      {{(states('sensor.nfl_team_news'))}} 
        ___
      ![Image]({{states.sensor.nfl_team_news.attributes["entity_picture"]}})
      {{states.sensor.nfl_team_news.attributes["caption"]}}
      1).[Mezzanine]({{states.sensor.nfl_team_news.attributes["mezzanine"]}})
      2).[HD]({{states.sensor.nfl_team_news.attributes["hd"]}})
      3).[HLS]({{states.sensor.nfl_team_news.attributes["hls"]}})
  - type: markdown
    content: >-
      {{(states('sensor.nfl_team_news_alt'))}} 
        ___
      ![Image]({{states.sensor.nfl_team_news_alt.attributes["entity_picture"]}})
      {{states.sensor.nfl_team_news_alt.attributes["caption"]}}
      1).[Mezzanine]({{states.sensor.nfl_team_news_alt.attributes["mezzanine"]}})
      2).[HD]({{states.sensor.nfl_team_news_alt.attributes["hd"]}})
      3).[HLS]({{states.sensor.nfl_team_news_alt.attributes["hls"]}})

nodered import:

[{"id":"e4b286ed5577ce9c","type":"www-request","z":"8f4d3eb158748da5","name":"all news","method":"POST","ret":"obj","url":"http://site.api.espn.com/apis/site/v2/sports/football/nfl/news","follow-redirects":true,"persistent-http":true,"tls":"","x":240,"y":40,"wires":[["943fae5448eaa432"]]},{"id":"1581cefc08921ec8","type":"inject","z":"8f4d3eb158748da5","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":40,"wires":[["e4b286ed5577ce9c"]]},{"id":"943fae5448eaa432","type":"change","z":"8f4d3eb158748da5","name":"articles","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.articles","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":40,"wires":[["caba0f6d3b25ccd7"]]},{"id":"caba0f6d3b25ccd7","type":"function","z":"8f4d3eb158748da5","name":"CHANGE TEAM HERE","func":"msg.payload = msg.payload.filter(el => el.description.includes(\"Bears\"))\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":40,"wires":[["036e5e3009994170"]]},{"id":"036e5e3009994170","type":"change","z":"8f4d3eb158748da5","name":"link url","rules":[{"t":"set","p":"link","pt":"msg","to":"payload.links.api.news.href","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":110,"y":100,"wires":[["1b6024f96ef113e3","dd22d5ae6bbbef0e"]]},{"id":"c58a56d1d7457836","type":"debug","z":"8f4d3eb158748da5","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":870,"y":220,"wires":[]},{"id":"1b6024f96ef113e3","type":"www-request","z":"8f4d3eb158748da5","name":"team link for article","method":"GET","ret":"obj","url":"{{{link.0}}}","follow-redirects":true,"persistent-http":true,"tls":"","x":310,"y":220,"wires":[["af345639c78e434f"]]},{"id":"af345639c78e434f","type":"change","z":"8f4d3eb158748da5","name":"videos","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.videos[0]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":220,"wires":[["d03c7c2717517de0"]]},{"id":"d03c7c2717517de0","type":"ha-entity","z":"8f4d3eb158748da5","name":"nfl_team_news","server":"d363f6df.2ec558","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"nfl_team_news"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"payload.headline","stateType":"msg","attributes":[{"property":"caption","value":"payload.caption","valueType":"msg"},{"property":"entity_picture","value":"payload.thumbnail","valueType":"msg"},{"property":"Mezzanine","value":"payload.links.source.mezzanine.href","valueType":"msg"},{"property":"HD","value":"payload.links.source.HD.href","valueType":"msg"},{"property":"HLS","value":"payload.links.source.HLS.href","valueType":"msg"}],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":620,"y":220,"wires":[["c58a56d1d7457836"]]},{"id":"439d3c6e028842ae","type":"debug","z":"8f4d3eb158748da5","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":870,"y":320,"wires":[]},{"id":"dd22d5ae6bbbef0e","type":"www-request","z":"8f4d3eb158748da5","name":"team link for article","method":"GET","ret":"obj","url":"{{{link.1}}}","follow-redirects":true,"persistent-http":true,"tls":"","x":230,"y":340,"wires":[["4d60d80a3baea755"]]},{"id":"4d60d80a3baea755","type":"change","z":"8f4d3eb158748da5","name":"videos","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.videos[0]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":340,"wires":[["cf9d1511ad38dcb3"]]},{"id":"cf9d1511ad38dcb3","type":"ha-entity","z":"8f4d3eb158748da5","name":"nfl_team_news_alt","server":"d363f6df.2ec558","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"nfl_team_news_alt"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"payload.headline","stateType":"msg","attributes":[{"property":"caption","value":"payload.caption","valueType":"msg"},{"property":"entity_picture","value":"payload.thumbnail","valueType":"msg"},{"property":"Mezzanine","value":"payload.links.source.mezzanine.href","valueType":"msg"},{"property":"HD","value":"payload.links.source.HD.href","valueType":"msg"},{"property":"HLS","value":"payload.links.source.HLS.href","valueType":"msg"}],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":650,"y":340,"wires":[["439d3c6e028842ae"]]},{"id":"d363f6df.2ec558","type":"server","name":"Home Assistant","version":1,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

Explain the update interval and the update timeout. I had mine updating every 2 seconds last weekend, during the game. Had the lights changing when the Chiefs had possession. Can you dump the update interval into seconds from minutes? Love your smart update interval idea.

I used Node Red to make mine update fast after PRE and it stopped on POST

The timeout shouldn’t really matter, and yah the interval is minutes. I could make it seconds, I just wanted to avoid it spamming the API too much. Even a few dozen people hitting it every two seconds would be noticeable I think. I’m hoping to try making it smarter this evening.

For those wanting to switch to the custom component, here are the template sensors, and lovelace cards I’m using.

Also, if the smart update interval isn’t completed by the weekend, I would think it would still possible to use an automation to force update sensor.nfl in the interim.

Note: Last Play & Win Probability are conditional and only appear during game time. Only shown here with null values as a preview. This has not been tested in game yet.

light mode

dark mode

Template Sensors

sensor:
################ NFL SCOREBOARD ################
  - platform: template
    sensors:
      nfl_team_timeouts:
        value_template: >
          {% if state_attr('sensor.nfl', 'team_timeouts') == 3 %}
            •••
          {% elif state_attr('sensor.nfl', 'team_timeouts') == 2 %}
            ••
          {% elif state_attr('sensor.nfl', 'team_timeouts') == 1 %}
            •
          {% else %}

          {% endif %}
        friendly_name_template: >
          {{ state_attr('sensor.nfl', 'team_name') }} Timeouts Remaining
      nfl_team_possession:
        value_template: >
          {% if state_attr('sensor.nfl', 'possession') == state_attr('sensor.nfl', 'team_id') %}
            🏈
          {% else %}

          {% endif %}
      nfl_team_score:
        value_template: >
          {{ states('sensor.nfl_team_timeouts') }} {{ state_attr('sensor.nfl', 'team_score') }}
        friendly_name_template: >
          {{ state_attr('sensor.nfl', 'team_name') }} {{ states('sensor.nfl_team_possession') }}
        entity_picture_template: >
          {{ state_attr('sensor.nfl', 'team_logo') }}
      nfl_opponent_timeouts:
        value_template: >
          {% if state_attr('sensor.nfl', 'opponent_timeouts') == 3 %}
            •••
          {% elif state_attr('sensor.nfl', 'opponent_timeouts') == 2 %}
            ••
          {% elif state_attr('sensor.nfl', 'opponent_timeouts') == 1 %}
            •
          {% else %}

          {% endif %}
        friendly_name_template: >
          {{ state_attr('sensor.nfl', 'opponent_name') }} Timeouts Remaining
      nfl_opponent_possession:
        value_template: >
          {% if state_attr('sensor.nfl', 'possession') == state_attr('sensor.nfl', 'opponent_id') %}
            🏈
          {% else %}

          {% endif %}
      nfl_opponent_score:
        value_template: >
          {{ states('sensor.nfl_opponent_timeouts') }} {{ state_attr('sensor.nfl', 'opponent_score') }}
        friendly_name_template: >
          {{ state_attr('sensor.nfl', 'opponent_name') }} {{ states('sensor.nfl_opponent_possession') }}
        entity_picture_template: >
          {{ state_attr('sensor.nfl', 'opponent_logo') }}
      nfl_sub_1:
        value_template: >
          {% if states('sensor.nfl') == 'PRE' %}
            Line: {{ state_attr('sensor.nfl', 'odds') }}
          {% elif states('sensor.nfl') == 'POST' %}
            Final
          {% else %}
            {{ state_attr('sensor.nfl', 'quarter') }} - {{ state_attr('sensor.nfl', 'clock') }}
          {% endif %}
        friendly_name_template: > 
          {{ as_timestamp(state_attr('sensor.nfl', 'date')) | timestamp_custom("%A @ %-I:%M %p") }}
      nfl_sub_2:
        value_template: >
          {% if states('sensor.nfl') == 'PRE' %}
            O/U: {{ state_attr('sensor.nfl', 'overunder') }}
          {% elif states('sensor.nfl') == 'POST' %}
            
          {% else %}
            {{ state_attr('sensor.nfl', 'down_distance_text') }}
          {% endif %}
        friendly_name_template: >
          {{ state_attr('sensor.nfl', 'venue') }}
      nfl_sub_3:
        value_template: >
          {{ state_attr('sensor.nfl', 'tv_network') }}
        friendly_name_template: >
          {{ state_attr('sensor.nfl', 'location') }}
      nfl_team_win_probability:
          value_template: >
            {{ state_attr('sensor.nfl', 'team_win_probability') | float * 100 }}
          friendly_name_template: >
            {{ state_attr('sensor.nfl', 'team_name') }}
      nfl_opponent_win_probability:
          value_template: >
            {{ state_attr('sensor.nfl', 'opponent_win_probability') | float * 100 }}
          friendly_name_template: >
            {{ state_attr('sensor.nfl', 'opponent_name') }}

Lovelace Cards
(uses card-mod, mini graph card, card templater, & card-tools)

      - type: vertical-stack
        cards:
          - type: entities
            title: Scoreboard
            card_mod:
              style: |
                div#states > div {
                  margin: 0px;
                }
            entities:
              - entity: sensor.nfl_team_score
                card_mod:
                  style: 
                    hui-generic-entity-row: 
                      $: |
                        .info {
                          font-size: 1.6em;
                          font-weight: 500;
                          padding: 6px 0;
                        }
                      .: |
                        .text-content {
                          font-size: 1.6em;
                          font-weight: 500;
                          padding: 6px 0;
                        }
              - entity: sensor.nfl_opponent_score
                card_mod:
                  style:
                    hui-generic-entity-row: 
                      $: |
                        .info {
                          font-size: 1.6em;
                          font-weight: 500;
                          padding: 4px 0 8px;
                        }
                      .: |
                        .text-content {
                          font-size: 1.6em;
                          font-weight: 500;
                          padding: 4px 0 8px;
                        }
              - type: divider
                card_mod:
                  style: |
                      div {
                        margin: 4px 0 8px !important;
                      }
                    
              - entity: sensor.nfl_sub_1
                card_mod:
                  style:
                    hui-generic-entity-row:
                      .: |
                        .text-content {
                          font-size: 1.2em;
                        }
                      $: |
                        state-badge {display:none;}
                        .text-content {
                          margin-left: 0px !important;
                          font-size: 1.2em;
                        }
              - entity: sensor.nfl_sub_2
                card_mod:
                  style:
                    hui-generic-entity-row:
                      .: |
                        .text-content {
                          font-weight: 300;
                        }
                      $: |
                        state-badge {display:none;}
                        .text-content {
                          margin-left: 0px !important;
                          font-weight: 300;
                        }
              - entity: sensor.nfl_sub_3
                card_mod:
                  style:
                    hui-generic-entity-row:
                      .: |
                        .text-content {
                          font-weight: 300;
                        }
                      $: |
                        state-badge {display:none;}
                        .text-content {
                          margin-left: 0px !important;
                          font-weight: 300;
                        }
          - type: conditional
            conditions:
              - entity: sensor.nfl
                state_not: 'PRE'
              - entity: sensor.nfl
                state_not: 'POST'
            card:
              type: 'custom:button-card'
              card_mod:
                style: 
                  div#container: |
                    div#name {
                      width: 100%;
                    }
              name: |
                [[[return `<div style='display:flex'>
                  <marquee>
                    ${states['sensor.nfl'].attributes.last_play}
                  </marquee>`]]]
              styles:
                card:
                  - padding: 8px
                  - font-size: 15px
          - type: conditional
            conditions:
              - entity: sensor.nfl
                state_not: 'PRE'
              - entity: sensor.nfl
                state_not: 'POST'
            card:
              type: 'custom:card-templater'
              entities:
               - sensor.nfl_team_win_probability
               - sensor.nfl_opponent_win_probability
               - sensor.nfl
               - input_boolean.dark_mode
              card:
                type: 'custom:mini-graph-card'
                name: Win Probability
                icon: 'mdi:football'
                hours_to_show: 2
                points_per_hour: 120
                aggregate_func: last
                unit: '%'
                entities:
                  - entity: sensor.nfl_team_win_probability
                    color_template: >
                      {% if states('input_boolean.dark_mode') == 'on' %}
                       {{ state_attr('sensor.nfl', 'team_colors')[1] }}
                      {% else %}
                       {{ state_attr('sensor.nfl', 'team_colors')[0] }}
                      {% endif %}
                  - entity: sensor.nfl_opponent_win_probability
                    color_template: >
                      {% if states('input_boolean.dark_mode') == 'on' %}
                       {{ state_attr('sensor.nfl', 'opponent_colors')[1] }}
                      {% else %}
                       {{ state_attr('sensor.nfl', 'opponent_colors')[0] }}
                      {% endif %}

Edit 1: Turned off graph animation, fixed timeout sensors.
Edit 2: change FINAL to POST

Nice, I’m not sure what the etiquette is for having cards in a custom_component but feel free to submit a PR. I believe the packages subdir is where things like this would go!

If anyone wants to check out the custom component it’s at github.com/zacs/ha-nfl — I’ll add to the parent post when it gets a bit cleaner.

Ideally, I’d like to build a custom card with a more standard scoreboard format. Something closer to this
image

I’m still working out the HTML and CSS to make it more appealing, as well as adding possession and timeouts etc. I’m not sure I can work out a custom graph in it, but I’d like to get the rest of the info including the last play. And then I have to figure out how to turn that into a custom card. If anyone has any suggestions on something existing I can fork, it would be appreciated.

Edit: got a bit further and have a decent html/css base to work from.
image
I might try to incorporate the text elements in a more creative way. I also found the boilerplate HACS card, so I’m looking into that but it’ll take quite a bit of reading / trial and error.

Can you post this code!

I’m getting UNAVAILABLE on the integration since doing the HACS update.

Yah, hit an issue where the odds field went away and broke things. It’s already fixed – just re-pull from the repo!

edit: Live debugging is fun, just fixed another issue with down distance only being present half the time.

I did a git pull, waiting for HA to reboot.

Yah wish it didn’t need to reboot. My test sensors have all been up for a bit now and issues are resolved (for now…).

@zacs It’s up and running. All good here.

It’s just static html right now. I’m working on turning it into a custom card for @zacs custom component.

edit: pretty happy with the layout, but I’m open to any suggestions
in game
image

pregame
image

I guess I’ll have to make a post-game layout as well.

1 Like

The win probability kept regenerating over and over and over. I need to figure that out. Man it looked pretty though.

you could remove animate: true from the probability card. I’m guessing it has something to do with the component refresh. It might still flash quick, but it won’t do the slow redraw of the graph.

I still seem to be getting some errors with downDistanceText which happens anytime a team scores. Using build ab614e6, tried reinstalling, restarting but still seems to be an issue.
Error fetching NFL data: 'downDistanceText'

Yep, that and possession both disappear in between possessions. I got a ton of errors as well, but once the game got going again it was all good. I know how to fix, but sadly haven’t been able to yet today. Should get to it tonight. Currently mourning my poor Seahawks defense.

1 Like

Yah, the component I based mine off resets all values to null in between refreshes. I’m going to experiment with not doing that while the MNF game goes tomorrow. Hoping to have all this buttoned up tomorrow so we are good for a bug-free next Sunday :).