lol, i thought at least twice that it must be a typo, but still did go on writing
it was very late and sometimes i cant stop myself
the error you get is because i missed something
the schedule card shows also if the game is AT or not.
but the structure from the 2 type of gamecards is different.
so i added some checks and and Type for to the sensor.
###########################################################################################
# #
# Rene Tode ( [email protected] ) #
# #
# 2018/10/07 Germany #
# #
# #
# an app to that creates a sensor out of data collected from #
# https://www.clevelandbrowns.com/schedule/ #
# #
###########################################################################################
import appdaemon.plugins.hass.hassapi as hass
import datetime
import time
import requests
from socket import timeout
from bs4 import BeautifulSoup
class browns(hass.Hass):
def initialize(self):
#################################################################
# when initialising the sensor needs to be imported #
# but we need to run the same code again to get the next values #
# thats why i only start the call_back from here #
#################################################################
self.get_values(self)
def get_values(self,kwargs):
#################################################################
# first we set some values, this could be done in the yaml #
# but this app is specialized and will only work for this #
# webpage, so why bother #
#################################################################
self.url = "https://www.clevelandbrowns.com/schedule/"
self.sensorname = "sensor.browns"
self.friendly_name = "Next game from Cleveland Browns"
next_game_time = None
#################################################################
# now we read the webpage #
#################################################################
try:
response = requests.get(self.url,timeout=10)
except:
self.log("i couldnt read the browns schedule page")
return
page = response.content
#################################################################
# now that we got the webpage we make the data readable #
#################################################################
soup = BeautifulSoup(page, "html.parser")
#################################################################
# in the google chrome console we are going down the tree from #
# body. every time an indention is visible we add the next #
# element. untill we see main, which contains a lot of section #
# elements. nextSibling makes us go to the next element on the #
# same level. untill we reach the table containing the schedule #
# cards. some invisible empty siblings make that we need more #
# rimes nextSibling then the amount of sections #
#################################################################
cards_table = soup.body.div.main.section.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling
#################################################################
# to see if we got the right data we log it. uncomment when #
# you expect that the webpage is changed #
#self.log(cards_table) #
#################################################################
#################################################################
# now we find the first card inside the table #
#################################################################
first_card = cards_table.div.div
#################################################################
# the first card is the title card containing "regular season" #
# now we are going to loop to the cards following that first 1 #
#################################################################
for schedule_card in first_card.find_next_siblings():
#############################################################
# lets find the date we want out of the card #
#############################################################
try:
game_start = schedule_card.div["data-gametime"]
except:
#########################################################
# there is no date found in this card (probably an add) #
#########################################################
game_start = ""
#############################################################
# if we find a date, then we need to translate the date to #
# a time we can compare. in this case we find a date like #
# like this 2018-09-09T17:00:00Z which is %Y-%m-%dT%H:%M:%S #
# (python datetime lib docs tell us that) #
#############################################################
if game_start != "":
game_time = datetime.datetime.strptime(game_start,"%Y-%m-%dT%H:%M:%SZ") - datetime.timedelta(hours= 4)
#########################################################
# find out if this date is in the future #
#########################################################
if game_time > datetime.datetime.now():
#####################################################
# check if we didnt find one before, when not set it#
#####################################################
if next_game_time == None:
next_game_time = game_time
#################################################
# now we need to check if the game is AT or not #
# because then the schedule card will be #
# different #
# so we try to change it, if not possible #
# then the game is not AT #
#################################################
try:
next_game_at = schedule_card.div.div.nextSibling.nextSibling.div.p.span.string.strip()
except:
next_game_at = "not AT"
#################################################
# now that we know that this is the next game #
# lets also lookup the opponent in the card #
# it will make a nice attribute for the sensor #
# to remove all whitespace we use strip() #
# again we can find that by looking at the #
# google chrome console #
#################################################
if next_game_at == "AT":
try:
opponent = schedule_card.div.div.nextSibling.nextSibling.div.div.p.string.strip()
except:
opponent = "not found"
else:
try:
opponent = schedule_card.div.div.nextSibling.nextSibling.p.nextSibling.nextSibling.string.strip()
except:
opponent = "not found"
#################################################
# and we want to find the channel that it will #
# be on. #
#################################################
channel = schedule_card.div.div.nextSibling.nextSibling.div.nextSibling.nextSibling.div.div.span.nextSibling.nextSibling.string.strip()
#################################################################
# now we got all data we need but the date isnt what we need #
# we translate that again to the timeformat we want to see #
# for the HA sensor #
#################################################################
next_game_str = next_game_time.strftime("%Y/%m/%d %H:%M:%S")
#################################################################
# now we got all info we need and we can create a sensor. #
# the first time that the code is run it will create a warning #
# that the sensor doesnt exist. if we see that in the log we #
# know that the sensor is created. #
#################################################################
#self.log("opponent = " + opponent + " , channel = " + channel)
self.set_state(self.sensorname, state = next_game_str, attributes = {"friendly_name": self.friendly_name,"Opponent": opponent,"Channel": channel,"Type": next_game_at})
#################################################################
# now al we need to do is make sure that the sensor stays up to #
# date. we could check the webpage every minute, but that would #
# be unneccesary traffic. we dont know exactly when the webpage #
# is updated, we need to use a short time after the game, but #
# we dont want it to be too long #
# if the sensor isnt up to date, just check the page, restart #
# the app and or change the extra time we now add #
#################################################################
update_time = next_game_time + datetime.timedelta(hours= 4)
#################################################################
# so we got a time that we want to update the sensor. so we run #
# this code again at that time. #
#################################################################
self.run_at(self.get_values,update_time)
#################################################################
# so the sensor is there and updating, so lets make sure a #
# a script will run to turn on the TV #
#################################################################
self.run_at(self.start_game,next_game_time - datetime.timedelta(minutes= self.args["offset_script"]),channel = channel)
def start_game(self,kwargs):
#################################################################
# so its time to turn on the TV so lets start the script with #
# the name of the channel #
#################################################################
if self.entity_exists("script." + kwargs["channel"]):
self.turn_on("script." + kwargs["channel"])
#################################################################
# and we want to start an automation some time after the script #
# has been started #
#################################################################
self.run_in(self.start_automation,self.args["seconds_automation_after_script"])
def start_automation(self,kwargs):
#################################################################
# so its time to start the automation #
#################################################################
if "automation_name" in self.args:
self.turn_on(self.args["automation_name"])
this is the complete code, to save you the trouble finding the part to copy/paste.
the automation you talk about is already started with this app!!
all you need to do is make sure you got the right settings in the yaml
browns:
module: browns
class: browns
offset_script: 30 # the time in minutes before the game the tv should be changed
seconds_automation_after_script: 900 # the time in seconds after the tv changed
automation_name: automation.browns_football #this is the entityname from the automation you created