Reading a sensor for automation

you should have called me earlier :wink:

it isnt that hard. with the info you gave in this topic i created a bit more code.
just paste this at the end from your browns app:

    #################################################################
    # 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                                       #
    #################################################################
    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"])

and add these last 3 lines to your browns.yaml

browns:
  module: browns
  class: browns
  offset_script: 15 # in minutes
  seconds_automation_after_script: 600 # in seconds
  automation_name: automation.browns_football

now all you need to make sure that the script is there and the automation.

i never work with scripts or automations, but i assume that you can find your scripts and automations in your statepage and the have names like this:

script.abc, script.fox, automation.browns_football

the only problem that can be there is that the script entity names are probably in lower case (please check) if so you need to change 1 more line in the code.

@Corey_Maxim Ok, I found the last issue. Missing a comma in this line when parsing the date_string:

{% set current_time = as_timestamp(strptime(current_time_string,'%Y-%m-%d, %H:%M')) %}

Soā€¦ This one should work if you donā€™t go with @ReneTodeā€™s solution:

sensor:
  - platform: template
    sensors:
      next_game:
        value_template: >
          {% set game_time_string = states('sensor.browns') %}
          {% set game_channel = state_attr('sensor.browns','Channel') %}
          {% set current_time_string = states('sensor.date__time') %}
          {% set five_minutes = 5 * 60 %}
          {% set game_time = as_timestamp(strptime(game_time_string,'%Y/%m/%d %H:%M:%S')) - five_minutes  %}
          {% set current_time = as_timestamp(strptime(current_time_string,'%Y-%m-%d, %H:%M')) %}
          {% set game_end = game_time + five_minutes + 3 * 60 * 60 %}
          {{ game_channel.lower() if game_time <= current_time <= game_end else 'off' }}
1 Like

Yes you are correct, the states page is lower case but the friendly name is uppercase (Iā€™m not sure if it matters)
Iā€™ll gladly give it a go!
Thanks @renetode I appreciate your help more then you know! I honestly try not to bother people anymore then I need too.

Iā€™m really trying to learn from all of these experiences so I can inturn pay it forward. I spend hours each day researching and looking at posts but mostly get no where. But itā€™s people like you and @petro and others that reach out and go above and beyond to help a stranger out. And in turn I feel like I learn a bit more each time.

This community is the best with some of the smartest, kindest individuals in the world and Iā€™m truly blessed to be a part of it!

@petro, I still can use you code for other things that I want to do with my sensor! Your hard work will still be very useful! Is there a way to show the time to the front end without the 0 in front of the time like 1:00 PM instead of 01:00 PM?

people do love to help you, because you really show appreciation and way more then the usual thanks :wink:
i am glad i can help you to learn things.
i did overestimate your capabilities maybe a bit and did let you swim, but it seems that that worked out quite nice, because it helped you to learn :wink:

because the scripts are lowercase and the website gives it as uppercase you need to change this line:

   self.turn_on("script." + kwargs["channel"])

to this line

   self.turn_on("script." + kwargs["channel"].lower())

its probably not neccesary, but to make sure, please restart appdaemon after you changed all and look at the logs if you see anything strange.

edit: i am pretty confident that this works, but i never started scripts or automations from AD, so please keep your fingers crossed that it works as expected :wink:

restarting now and my fingers are crossed!, I always want people to know that they are appreciated I see way too many people here complain when things donā€™t work as expected or when thereā€™s breaking changes. I just shake my head and donā€™t understand why, this is a free opensource project that is completely awesome. Since stumbling upon Home Assistant my home automation has done so much good, fun and useful things that I could never begin to imagine or afford without it. I has opened my eyes to the possibilities that can be done that I never dreamed of. I can see where the future is going and I canā€™t wait to get there!

2 Likes

The Automation is showing now!!!

everything seems good

and btw, I donā€™t mind swimming to learn as long as I donā€™t break things lol (I have in the past)

I canā€™t wait until tomorrow to see if it works!!! lol

1 Like

Yeah, are you using the sensor.time sensor? If so, add this sensor template and hide the other one:

sensor:
  - platform: template
    sensors:
      pretty_time:
        value_template: >
          {% set ct = states('sensor.date__time') %}
          {% set ct = as_timestamp(strptime(ct,'%Y-%m-%d, %H:%M')) %}
          {% set ct = ct | timestamp_custom("%H:%M %p") %}
          {{ ct[1:] if ct.startswith('0') else ct }}
  • first line gets the string from the date__time sensor
  • second line turns it into a timestamp (number of seconds from jan 1st 1970)
  • third line makes it into a custom string. You may need to turn %H into %I. I forget which one is 24/hr format and which one is 12hr format. Pretty sure %H is 12 hour.
  • fourth line checks to see if the time starts with a zero. If it does, output everything past the zero. Otherwise, keep it.

The 4th line needs to be written that way because the modifiers for custom timestamps are not allowed. In python, there is a way to remove leading zeros when outputting time. For some reason, that method doesnā€™t work in jinja templates.

2 Likes

%H is 24 hour, %I is 12 hour.

And the method to strip the leading 0 works for custom timestamps in jinja too.

At least it does for this one:

{{ as_timestamp(state_attr('sensor.nws_alert_event', 'features')[0].properties.expires)| timestamp_custom('%-I:%M %p on %-m-%-d-%Y') }}

1 Like

Interesting, that must have changed in recent builds. Good to know. Or maybe I was thinking of strftime. Either way, @Corey_Maxim this will work then:

sensor:
  - platform: template
    sensors:
      pretty_time:
        value_template: >
          {% set ct = states('sensor.date__time') %}
          {% set ct = as_timestamp(strptime(ct,'%Y-%m-%d, %H:%M')) %}
          {{ ct | timestamp_custom("%-I:%M %p") }}
1 Like

Thanks guys itā€™s perfect!

@ReneTode

For some reason all of my addons turned off tonight, so I switched them back on then restarted hass.
now for some reason my sensor.browns and my automation is not showing up do you have any ideas why?
OMG, I think they change the schedule site, im about to cryā€¦ damn

if I get time next week, I might give this site a go, this does show what I need to find i think?

###########################################################################################
# #
# 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 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                         #
                    #################################################
                    opponent = schedule_card.div.div.nextSibling.nextSibling.p.nextSibling.nextSibling.string.strip()
                    #################################################
                    # 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("%I:%M %p %m/%d/%y")
    #################################################################
    # 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.set_state(self.sensorname, state = next_game_str, attributes = {"friendly_name": self.friendly_name,"Opponent": opponent,"Channel": channel})
    #################################################################
    # 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                                       #
    #################################################################
    self.turn_on("script." + kwargs["channel"].lower())
    #################################################################
    # 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"])

you need to check your logfiles from appdaemon to see what went wrong.
there must be errors so that the sensor doesnt get created.

edit: i just restarted the app myself and i saw whats wrong.
i see that i have 2 small errors this line:

    self.run_at(self.start_game,next_game_time - datetime.timedelta(minutes= self.args["offset_script"],channel = channel)

needs to have an extra ) so becomes this:

    self.run_at(self.start_game,next_game_time - datetime.timedelta(minutes= self.args["offset_script"]),channel = channel)

and in this line:

  def start_automation(self,kwargs)

i missed a : so it becomes

  def start_automation(self,kwargs):

i hope you can change that in time, because when you start the app inside the offset period it will also throw an error.

I changed the lines, but they changed the Cleveland Browns.com/schedule web sight completely so now all the information is not on the page like it was. Iā€™m going to have to try that new one I posted above because it does show all the info and more the old one had. I canā€™t believe it lol.

I might look into trying to pull the info from google calendar next season that way it will be more stable each season. The link above letā€™s you import to your Calendar.

i didnt see any change.
still works for me

2018-10-14 10:21:31.327621 INFO AppDaemon: Loading App Module: /config/appdaemon/apps/browns.py 2018-10-14 10:21:31.331177 WARNING AppDaemon: ------------------------------------------------------------ 2018-10-14 10:21:31.331758 WARNING AppDaemon: Unexpected error loading module: /config/appdaemon/apps/browns.py: 2018-10-14 10:21:31.332307 WARNING AppDaemon: ------------------------------------------------------------ 2018-10-14 10:21:31.338892 WARNING AppDaemon: Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 2015, in check_app_updates self.read_app(mod["name"], mod["reload"]) File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 1802, in read_app self.modules[module_name] = importlib.import_module(module_name) File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "&lt;frozen importlib._bootstrap&gt;", line 994, in _gcd_import File "&lt;frozen importlib._bootstrap&gt;", line 971, in _find_and_load File "&lt;frozen importlib._bootstrap&gt;", line 955, in _find_and_load_unlocked File "&lt;frozen importlib._bootstrap&gt;", line 665, in _load_unlocked File "&lt;frozen importlib._bootstrap_external&gt;", line 674, in exec_module File "&lt;frozen importlib._bootstrap_external&gt;", line 781, in get_code File "&lt;frozen importlib._bootstrap_external&gt;", line 741, in source_to_code File "&lt;frozen importlib._bootstrap&gt;", line 219, in _call_with_frames_removed File "/config/appdaemon/apps/browns.py", line 131 self.run_at(self.start_game,next_game_time - datetime.timedelta(minutes= self.args["offset_script"]),channel = channel) ^ IndentationError: unexpected indent 2018-10-14 10:21:31.339469 WARNING AppDaemon: ------------------------------------------------------------ 2018-10-14 10:21:31.339971 WARNING AppDaemon: Removing associated apps: 2018-10-14 10:21:31.340624 WARNING AppDaemon: browns

ok I found a few things, but im still getting errors. Im trying! ###########################################################################################
# #
# 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 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                         #
                    #################################################
                    opponent = schedule_card.div.div.nextSibling.nextSibling.p.nextSibling.nextSibling.string.strip()
                    #################################################
                    # 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("%I:%M %p %m/%d/%y")
    #################################################################
    # 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.run_at(self.start_game,next_game_time - datetime.timedelta(minutes= self.args["offset_script"]),channel = channel)
    #################################################################
    # 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                                       #
    #################################################################
    self.turn_on("script." + kwargs["channel"].lower())
    #################################################################
    # 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"])

the old line had a reference to a sensor, this one doesnā€™t should it?

self.run_at(self.start_game,next_game_time - datetime.timedelta(minutes= self.args["offset_script"]),channel = channel)

Never mind, I think I found it, I had one of the addon line duplicated in the code, the sensor is back so hopefully were good to go!

I do have a question for anybody who may see this or knows,

how do I go about starting a automation from a automation?

I would like to add automations after the appdamon automation starts!

If you want the automation to trigger regardless of the other triggers or conditions then all you need to do is call the automation.trigger service.

action:
  - service: automation.trigger
    entity_id: automation.whichever_you_want