Reading a sensor for automation

lol, i thought at least twice that it must be a typo, but still did go on writing :wink:
it was very late and sometimes i cant stop myself :wink:

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
1 Like

Awesome! Thank You! question,

Is there a way to format the code how the sensor information shows in the front end to look like 10//22/18 1:00 PM (Without the leading 0) not a big deal but would rather the 0 not be included just for display purpose) and is there a way to extract the Opponent and channel information so I can have my Alexa announce what’s coming up on the card.

I wasn’t sure how to pull the gathered attributes from the sensor after it’s created.

Either way I appreciate you and the time you put in this for me.

If any of this doesn’t make any sense forgive me as it’s Beaty early morning here and I didn’t have my coffee yet lol

you already found out in this topic how to get rid of the leading zero!

you need to change the format from the strftime (if i did look back correctly you need to replace %H with %-H)
so now read in the code and find where to change it :wink:

if you have trouble with using attributes, you could easyly create 3 sensors instead of just 1.
with this line you create and update the sensor:

    self.set_state(self.sensorname, state = next_game_str, attributes = {"friendly_name": self.friendly_name,"Opponent": opponent,"Channel": channel,"Type": next_game_at})

self.sensorname is a variable that we did set in the first part of the app. its just an entityname (sensor.browns)
and with that line of code you also set the attrinutes
now you can add 2 more lines:

    self.set_state("sensor.browns_next_opponent, state = opponent)
    self.set_state("sensor.browns_channel_next_game, state = channel)

off course you could also try to find out how to use the attributes (i usually use the way that is easiest for me :wink: )

1 Like

I can’t seem to figure out where to place these, I did try here but got errors.

#################################################################
#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})
self.set_state("sensor.browns_next_opponent, state = opponent) 
self.set_state("sensor.browns_channel_next_game, state = channel)

missing a quote:

self.set_state("sensor.browns_next_opponent", state = opponent)
self.set_state("sensor.browns_channel_next_game", state = channel)

Do you use a text editor? You should use something that adjusts colors of your code based on the context. You’d be able to see missing quotes, missing parenthesis, and punctuation. If you don’t want an IDE, go with notepad++. Its free and it will colorize your junk based on file type.

Just to highlight your issue, this is what you wrote: (Notice the weird coloring?)

self.set_state("sensor.browns_next_opponent, state = opponent)
self.set_state("sensor.browns_channel_next_game, state = channel)

vrs

self.set_state("sensor.browns_next_opponent", state = opponent)
self.set_state("sensor.browns_channel_next_game", state = channel)

Yea, I do and I should have caught that!

Thanks for pointing that out, I think maybe I was too focused on where to place it!

Thanks @petro

Also Thanks @ReneTode for helping me once again!

sorry that forgot a quote :wink:
that kind of things happen to me all the time and mostly i find out by the errors.

but i am glad petro caught it and pointed you to a way to notice those things yourself.

1 Like