Mostrar jogos de futebol com transmissão televisiva

Como adepto de futebol, procurei uma maneira simples de saber quais os jogos com transmissão para o dia atual e o dia seguinte.

Através do Node Red e utilizando um scrape com base no site https://www.futebol365.pt/jogos-na-tv/, saquei a informação dos jogos, criei 2 entidades no HA com a info anterior e através de um template mostro a hora, jogo e canal.

Vamos por partes:

  1. Importar o seguinte flow para o Node Red e instalar a pallete node-red-contrib-cron-plus

Este flow está configurado para ser executado de hora em hora de forma a atualizar os dados dos seguintes sensores:
sensor:sensor.today_footbal_games
sensor.tomorrow_footbal_games

Através do node Inject, podem executar o flow e os sensores já ficam com dados.

[{"id":"b2b3b890.cbc99","type":"tab","label":"Futebol TV Sensores","disabled":false,"info":""},{"id":"ef5efef0.d8e0a8","type":"ha-entity","z":"b2b3b890.cbc99","name":"","server":"97033d39.2965b","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"Today Footbal Games"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"payload[0].date","stateType":"msg","attributes":[{"property":"date","value":"payload[0].date","valueType":"msg"},{"property":"games","value":"payload[0].games","valueType":"msg"},{"property":"updatedOn","value":"payload[0].updatedOn","valueType":"msg"}],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":850,"y":520,"wires":[[]]},{"id":"f6800d57.400f48","type":"ha-entity","z":"b2b3b890.cbc99","name":"","server":"97033d39.2965b","version":1,"debugenabled":false,"outputs":1,"entityType":"sensor","config":[{"property":"name","value":"Tomorrow Footbal Games"},{"property":"device_class","value":""},{"property":"icon","value":""},{"property":"unit_of_measurement","value":""}],"state":"payload[1].date","stateType":"msg","attributes":[{"property":"date","value":"payload[1].date","valueType":"msg"},{"property":"games","value":"payload[1].games","valueType":"msg"},{"property":"updatedOn","value":"payload[1].updatedOn","valueType":"msg"}],"resend":true,"outputLocation":"","outputLocationType":"none","inputOverride":"allow","outputOnStateChange":false,"outputPayload":"$entity().state ? \"on\": \"off\"","outputPayloadType":"jsonata","x":1030,"y":360,"wires":[[]]},{"id":"a1263a91.9e7468","type":"inject","z":"b2b3b890.cbc99","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":420,"wires":[["3f072ab4.ba1496"]]},{"id":"3f072ab4.ba1496","type":"http request","z":"b2b3b890.cbc99","name":"Obter Jogos na TV","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://www.futebol365.pt/jogos-na-tv/","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":260,"wires":[["d2136d3d.26309","d4859fee.65b8e8"]]},{"id":"d4859fee.65b8e8","type":"html","z":"b2b3b890.cbc99","name":"","property":"payload","outproperty":"payload","tag":".condensed-400.headerTitle.orange","ret":"html","as":"single","x":710,"y":180,"wires":[["78037343.d22454"]]},{"id":"d2136d3d.26309","type":"html","z":"b2b3b890.cbc99","name":"","property":"payload","outproperty":"payload","tag":".ink-table.alternating.ink-table-f365.double-bottom-space tbody","ret":"text","as":"single","x":750,"y":220,"wires":[["4bbff48a.d32534"]]},{"id":"e56cba68.7d367","type":"cronplus","z":"b2b3b890.cbc99","name":"","outputField":"payload","timeZone":"","persistDynamic":false,"commandResponseMsgOutput":"output1","outputs":1,"options":[{"name":"schedule1","topic":"schedule1","payloadType":"default","payload":"","expressionType":"cron","expression":"0 * * * *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":120,"y":280,"wires":[["3f072ab4.ba1496"]]},{"id":"b4f32ab2.973cc8","type":"join","z":"b2b3b890.cbc99","name":"Dates With Games","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":480,"y":360,"wires":[["831be36f.94dad8"]]},{"id":"831be36f.94dad8","type":"function","z":"b2b3b890.cbc99","name":"Merge Dates/Games","func":"var allGamesArr   = msg.payload[0];\nvar allDatesArr   = msg.payload[1].dates;\n\nvar allGamesByDate = [];\n\nvar date = new Date();\nvar dateStr =\n date.getFullYear()+ \"-\" +\n  (\"00\" + (date.getMonth() + 1)).slice(-2) + \"-\" +\n   (\"00\" + date.getDate()).slice(-2)  + \" \" +\n  (\"00\" + date.getHours()).slice(-2) + \":\" +\n  (\"00\" + date.getMinutes()).slice(-2) + \":\" +\n  (\"00\" + date.getSeconds()).slice(-2);\n\n\n\nfor(var i=0; i<allDatesArr.length; i++) {\n    \n    var dateInfo = {\n        'date' : allDatesArr[i].gamesDate,\n        'games' : [],\n        'updatedOn' :dateStr\n    };\n    \n    var dayGames = allGamesArr[i].games;\n    for(var t=0; t<dayGames.length; t++) {\n        dateInfo.games.push(dayGames[t]);\n    }\n    allGamesByDate.push(dateInfo);\n    \n    \n}\n\nreturn [{'payload': allGamesByDate}, null];","outputs":1,"noerr":0,"initialize":"","finalize":"","x":720,"y":360,"wires":[["f6800d57.400f48","ef5efef0.d8e0a8"]]},{"id":"78037343.d22454","type":"function","z":"b2b3b890.cbc99","name":"Get Dates","func":"var currentDate   = msg.payload[0];\n\nvar dates = [];\n\nfor(var i=0; i<msg.payload.length; i++) {\n  \n     \n     var dayNumber =  msg.payload[i].substring(\n         msg.payload[i].indexOf(\",\")+2, msg.payload[i].indexOf(\" de\"));\n    \n    \n    var monthName =  msg.payload[i].substring(\n        msg.payload[i].indexOf(\" de\")+4,\n        msg.payload[i].length\n        );\n        \n    var gamesDate = \"\";\n    \n    var monthNumber = -1;\n    if(monthName == \"Janeiro\") {\n        monthNumber = \"01\";\n    } else if(monthName == \"Fevereiro\") {\n        monthNumber = \"02\";\n    }  else if(monthName.indexOf(\"Mar\")) {\n        monthNumber = \"03\";\n    }  else if(monthName == \"Abril\") {\n        monthNumber = \"04\";\n    }  else if(monthName == \"Maio\") {\n        monthNumber = \"05\";\n    }  else if(monthName == \"Junho\") {\n        monthNumber = \"06\";\n    }  else if(monthName == \"Julho\") {\n        monthNumber = \"07\";\n    }  else if(monthName == \"Agosto\") {\n        monthNumber = \"08\";\n    }  else if(monthName == \"Setembro\") {\n        monthNumber = \"09\";\n    }  else if(monthName == \"Outubro\") {\n        monthNumber = \"10\";\n    }  else if(monthName == \"Novembro\") {\n        monthNumber = \"11\";\n    }  else if(monthName == \"Dezemrbo\") {\n        monthNumber = \"12\";\n    } \n    gamesDate = new Date().getFullYear()+\"-\"+monthNumber+\"-\"+dayNumber;\n    \n    dates.push({\n       'date' : msg.payload[i],\n       'dayNumber' : dayNumber,\n       'monthName' : monthName,\n       'year' : new Date().getFullYear(),\n       'index': i,\n       'gamesDate' : gamesDate\n    })\n}\nreturn [{'payload': {'dates': dates}}, null];","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1130,"y":180,"wires":[["b4f32ab2.973cc8"]]},{"id":"4bbff48a.d32534","type":"function","z":"b2b3b890.cbc99","name":"Get Games","func":"var allGames = [];\n\n\nfor(t=0; t<msg.payload.length; t++) {\n    var dayGamesArr = msg.payload[t].split(/\\r\\n|\\r|\\n/);\n    \n    var dayObj = {\n        'gameIndex': t,\n        'games' : []\n    };\n    \n    for(x=0; x<dayGamesArr.length; x++) {\n        \n        if(dayGamesArr[x] != null && dayGamesArr[x].length == 5 &&  dayGamesArr[x].indexOf(\":\") == 2 ) {\n       dayObj.games.push({\n           'time' : dayGamesArr[x],\n           'homeTeam' : dayGamesArr[x+1],\n           'awayTeam' : dayGamesArr[x+3],\n           'channel' : dayGamesArr[x+7]\n       });\n    }\n    }\n    allGames.push(dayObj);\n\n}\n\n\nreturn [{'payload': allGames}, null];","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1110,"y":240,"wires":[["b4f32ab2.973cc8"]]},{"id":"97033d39.2965b","type":"server","name":"HomeAssistant","legacy":false,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

Executem o flow e validem se no Developer Tools os dados estão como na seguinte imagem:

  1. Criar um Markdown para mostrar os jogos do dia atual com a seguinte informação:
> type: markdown
content: |
  Futebol na TV: {{state_attr('sensor.today_footbal_games','date') }}
   <font size="1">Atualizado a: {{state_attr('sensor.today_footbal_games','updatedon') }} </font> 
  <hr>
  {% for game in state_attr('sensor.today_footbal_games','games') %}
   [{{game.time}}] {{game.homeTeam}} vs {{game.awayTeam}} [{{game.channel}}]
   {% endfor %}

Resultado final do card:
image

Podem replicar o card para mostrar os jogos do dia seguinte, alterando apenas o nome do sensor.

7 Curtiram

Muito bom @vpnobrega obrigado pelo teu contributo.

Muito interessante e com grande potencial para outras automações. Obrigado pela partilha!

1 Curtiu

Sim, a base está lá… Tenho numa estrutura todos os dias/jogos que tá no site… Agora há que trabalhar os dados da melhor maneira :slight_smile:

O projecto está ótimo mas penso que ganharia um interesse ainda maior se fosse possível filtrar os jogos por país.

1 Curtiu

Teria que utilizar outra fonte de dados de forma a cruzar e saber qual o país/competição.

PS: Já encontrei uma fonte de dados que indica a competição e jogo, vou tentar obter essa info e juntar a competição/país à estrutura atual.

1 Curtiu

Obrigado pela partilha,

Já agora, se eu quiser apenas os jogos do Benfica, que tipo de IF deve ser feito?
Algo como if game.homeTeam=='Benfica' or game.awayTeam=='Benfica'?

1 Curtiu

Julgo que deve dar para filtar dessa maneira na construção do template.
Senão der, terá de ser feito no Node Red, na função que constroi o objeto com os dados do jogo.

P.S. Estou a tentar evoluir isto para mostrar o país e nome da competição, além da informação do jogo.

2 Curtiram

Boa partilha :+1:

No entanto no meu Node-Red aparece-me este erro quando executo o node inject:

Já tenho o Node-Red instalado :thinking:

Agora tens de ir ao HA e no HACS instalar a integração com o NodeRed.

image

E depois activas a integração aqui:

Open your Home Assistant instance and start setting up a new integration.

image

Boa tarde,

Em primeiro lugar quero agradecer ao @vpnobrega este excelente automatismo, e depois quero pedir-lhe ajuda :slight_smile: pois no meu HA, o mês de Março está a aparecer como Janeiro. Após colocar o debug no node-red, reparo que no node “.condensed-400.headerTitle.orange” que vai buscar as datas, a letra “ç” do mês de Março aparece mal “encodada” (Domingo, 14 de Mar&#xE7;o). Será alguma configuração de codepage do meu lado?

Obrigado desde já pela ajuda.

Viva, uma vez que o encoding não vem bem formado, na função onde verifico o nome do mês para obter o respetivo número, é necessário verificar se a string começa por “Mar” e em caso afirmativo, retorna 3 e tens o teu problema corrigido. Senão conseguires diz que depois coloco aqui a correção.

Obrigado pela dica. Já alterei o node (no mês de Março tinha este código monthName.indexOf(\"Mar\"), mas eu alterei para monthName.startsWith("Mar") e agora já mostra 03 no mês.

1 Curtiu

Em breve vou fazer uma nova publicação de um flow para criar 3 sensores de jogos (jogos de hoje, amanhã e depois de amanhã) e para enviar esta info para o Telegram.

Além dos jogos, o país e competição associado ao jogo também será apresentado.

2 Curtiram

Boas @vpnobrega, para mim está muito bom esta implementação dos jogos com a transissão televisiva :+1:, no entanto gostava de te questinar uma coisa…
Seria possível alterar o nome do sensor “sensor.today_footbal_games” para “sensor.today_football_games”? Isto é só questão ortográfica, mas comom estou a tentar fazer um template com ele, está me a dar um erro que não sei de onde vem. Se o mesmo template funciona.

Deixo aqui o meu template juntamente do erro, caso me consigas ajudar.

template:
  sensor:
    - unique_id: football
      state: template
      attributes:
        football: >
          {% for game in state_attr('sensor.today_footbal_games','games') %}
          [{{game.time}}] {{game.homeTeam}} vs {{game.awayTeam}} [{{game.channel}}]
          {% endfor %}

E agora os 2 erros:

Logger: homeassistant.helpers.event
Source: helpers/template.py:399
First occurred: 09:39:29 (1 occurrences)
Last logged: 09:39:29

Error while processing template: Template("{% for game in state_attr('sensor.today_footbal_games','games') %} [{{game.time}}] {{game.homeTeam}} vs {{game.awayTeam}} [{{game.channel}}] {% endfor %}")
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 397, in async_render
    render_result = _render_with_context(self.template, compiled, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1604, in _render_with_context
    return template.render(**kwargs)
  File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 1304, in render
    self.environment.handle_exception()
  File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 925, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "<template>", line 1, in top-level template code
TypeError: 'NoneType' object 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 513, in async_render_to_info
    render_info._result = self.async_render(variables, strict=strict, **kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 399, in async_render
    raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: TypeError: 'NoneType' object is not iterable
Logger: homeassistant.components.template.template_entity
Source: components/template/template_entity.py:73
Integration: Template (documentation, issues)
First occurred: 09:39:29 (1 occurrences)
Last logged: 09:39:29

TemplateError('TypeError: 'NoneType' object is not iterable') while processing template 'Template("{% for game in state_attr('sensor.today_footbal_games','games') %} [{{game.time}}] {{game.homeTeam}} vs {{game.awayTeam}} [{{game.channel}}] {% endfor %}")' for attribute 'football' in entity 'sensor.template_football'

Obrigado

Podes corrigir o nome através do node Red. O último node do flow é que indica o nome do sensor.

Vai ao developer tools e confirma de tens o sensor criado correctamente

Eu altero, mas ele só me atera o nome ( Friendly name)…

Copia o Node sem fazer a ligação, coloca o nome correto, apaga o outro node e faz deploy. Depois Fa a ligação ao último node e deve assumir o nome correcto.

1 Curtiu

Copyright © 2017-2021. Todos os direitos reservados
CPHA.pt - info@cpha.pt


FAQ | Termos de Serviço/Regras | Política de Privacidade