Appdaemon lightschedule

I was able to figure this out but had to move to tab delimiter to use the code mostly unchanged.

###########################################################################################
#                                                                                         #
#  Rene Tode ( [email protected] )                                                            #
#  ( with a lot off help from andrew cockburn (aimc) )                                    #
#  2016/11/28 Germany                                                                     #
#                                                                                         #
###########################################################################################

import appdaemon.appapi as appapi
import datetime
import time

class switches(appapi.AppDaemon):

  def initialize(self):
    file_name = self.args["settingsfile"]
    try:
      settings_file = open(file_name, "r")
      for line in settings_file:

        splitline = line.split("\t")
        if not "#" in splitline[0]:
          
          switch = splitline[0]
          if splitline[1]!="":
            ontime = self.parse_time(splitline[1])
          else:
            ontime = ""
          if splitline[2]!="":
            offtime = self.parse_time(splitline[2])
          else:
            offtime = ""
          constraindays = splitline[3]
          constraindays = constraindays.replace("\n", "")
          if type(ontime) is datetime.time:
            self.log(switch + " time: " + ontime.strftime("%H:%M:%S") + " day: " + constraindays + " on")
            self.run_daily(self.set_lights_on,ontime,constrain_days=constraindays,switch=switch)
          if type(offtime) is datetime.time:
            self.log(switch + " time: " + offtime.strftime("%H:%M:%S") + " day: " + constraindays + " off")
            self.run_daily(self.set_lights_off,offtime,constrain_days=constraindays,switch=switch)            
    except:
      self.log( "error in reading lightsetting file")


  def set_lights_on(self, kwargs):
    self.log(kwargs["switch"] + " on")
    self.turn_on(kwargs["switch"])
      
  def set_lights_off(self, kwargs):
    self.log(kwargs["switch"] + " off")
    self.turn_off(kwargs["switch"])

i guess it depends on how you save your files.
my version from excel (xp version) saves with ; as delimiter when i save the file as .csv

i also discovered another small problem. if you run this code and dont reinitialize for a long time, the sunset and sunrise will wander of, from the real sunset and sunrise.

i changed my code to:

###########################################################################################
#                                                                                         #
#  Rene Tode ( [email protected] )                                                            #
#  ( with a lot off help from andrew cockburn (aimc) )                                    #
#  2016/11/28 Germany                                                                     #
#                                                                                         #
###########################################################################################

import appdaemon.appapi as appapi
import datetime
import time

class switches(appapi.AppDaemon):

  def initialize(self):
    file_name = self.args["settingsfile"]
    sunriseon = False
    sunseton = False
    sunriseoff = False
    sunsetoff = False
    try:
      settings_file = open(file_name, "r")
      for line in settings_file:
        splitline = line.split(";")
        if not "#" in splitline[0]:
          switch = splitline[0]
          constraindays = splitline[3].rstrip('\n')
          sunseton = False
          sunriseon = False
          sunriseoff = False
          sunsetoff = False
          ontime = ""
          if splitline[1]!="":
            if "sunset" in splitline[1]:
              sunsetparts =splitline[1].split(":")
              offset1 = int(sunsetparts[1])
              sunseton = True
            elif "sunrise" in splitline[1]:
              sunsetparts =splitline[1].split(":")
              offset1 = int(sunsetparts[1])
              sunriseon = True
            else:
              ontime = self.parse_time(splitline[1])             
          else:
            ontime = ""
          if splitline[2]!="":
            if "sunset" in splitline[2]:
              sunsetparts =splitline[2].split(":")
              offset2 = int(sunsetparts[1])
              sunsetoff = True
            elif "sunrise" in splitline[2]:
              sunsetparts =splitline[2].split(":")
              offset2 = int(sunsetparts[1])
              sunriseoff = True
            else:
              offtime = self.parse_time(splitline[2])
          else:
            offtime = ""

          if sunriseon:
            self.log(switch + " time: sunrise " + str(offset1) + " day: " + constraindays + " on")
            self.run_at_sunrise(self.set_lights_on,offset=offset1,constrain_days=constraindays,switch=switch)
          if sunriseoff:
            self.log(switch + " time: sunrise " + str(offset2) + " day: " + constraindays + " off")
            self.run_at_sunrise(self.set_lights_off,offset=offset2,constrain_days=constraindays,switch=switch)
          if sunseton:
            self.log(switch + " time: sunset " + str(offset1) + " day: " + constraindays + " on")
            self.run_at_sunset(self.set_lights_on,offset=offset1,constrain_days=constraindays,switch=switch)
          if sunsetoff:
            self.log(switch + " time: sunset " + str(offset2) + " day: " + constraindays + " off")
            self.run_at_sunset(self.set_lights_off,offset=offset2,constrain_days=constraindays,switch=switch)            
          if type(ontime) is datetime.time:
            self.log(switch + " time: " + ontime.strftime("%H:%M:%S") + " day: " + constraindays + " on")
            self.run_daily(self.set_lights_on,ontime,constrain_days=constraindays,switch=switch)
          if type(offtime) is datetime.time:
            self.log(switch + " time: " + offtime.strftime("%H:%M:%S") + " day: " + constraindays + " off")
            self.run_daily(self.set_lights_off,offtime,constrain_days=constraindays,switch=switch)            
    except:
      self.log( "FAILURE IN READING LIGHTING FILE")
        
  def set_lights_on(self, kwargs):
    self.log(kwargs["switch"] + " turned on")
    self.light_action_log("automatisch aan",kwargs["switch"],"on")
    self.turn_on(kwargs["switch"])
      
  def set_lights_off(self, kwargs):
    self.log(kwargs["switch"] + " turned off")
    self.light_action_log("automatisch uit",kwargs["switch"],"off")
    self.turn_off(kwargs["switch"])

  def light_action_log(self,description, entity1, value1):
    runtime = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")
    try:
      log = open(self.args["logfile"], 'a')
      log.write(runtime + ";" + description + ";" + entity1 + ";" + value1 + "\n")
      log.close()
    except:
      self.log("LIGHTINGLOG NOT AVAILABLE!!")

This discussion originates from the AppDaemon thread, moving it here to keep with the topic

I don’t see it as an issue that you have two columns for on and off, it is just a different way of preprocessing and setting upp all data.

I still think you can get away with a very simple initialisation

you need to do either a run once or run daily, since you can create your time stamp

if run_once:
    self.run_once(self.callback, self.parse_time(sunrise+00:45:00), action="on")
else:
    self.run_daily(self.callback, self.parse_time(sunrise+00:45:00), action="on")

as for your suggestion of the need to reinitialise every so often for the sunset, sunrise sliding over time I’m thinking about just having a run_daily (or weekly) that schedules all run_once instead. But I will think a bit more about it prior to doing that.

/R

i could simplefy a little more by making this part:

            if "sunset" in splitline[1]:
              sunsetparts =splitline[1].split(":")
              offset1 = int(sunsetparts[1])
              sunseton = True
            elif "sunrise" in splitline[1]:
              sunsetparts =splitline[1].split(":")
              offset1 = int(sunsetparts[1])
              sunriseon = True
            else:
              ontime = self.parse_time(splitline[1])             

into a function and that would be called for the on and off column
and by using service call instead of turn on and off i could bring down these 6 ifs to 3:

          if sunriseon:
            self.log(switch + " time: sunrise " + str(offset1) + " day: " + constraindays + " on")
            self.run_at_sunrise(self.set_lights_on,offset=offset1,constrain_days=constraindays,switch=switch)
          if sunriseoff:
            self.log(switch + " time: sunrise " + str(offset2) + " day: " + constraindays + " off")
            self.run_at_sunrise(self.set_lights_off,offset=offset2,constrain_days=constraindays,switch=switch)
          if sunseton:
            self.log(switch + " time: sunset " + str(offset1) + " day: " + constraindays + " on")
            self.run_at_sunset(self.set_lights_on,offset=offset1,constrain_days=constraindays,switch=switch)
          if sunsetoff:
            self.log(switch + " time: sunset " + str(offset2) + " day: " + constraindays + " off")
            self.run_at_sunset(self.set_lights_off,offset=offset2,constrain_days=constraindays,switch=switch)            
          if type(ontime) is datetime.time:
            self.log(switch + " time: " + ontime.strftime("%H:%M:%S") + " day: " + constraindays + " on")
            self.run_daily(self.set_lights_on,ontime,constrain_days=constraindays,switch=switch)
          if type(offtime) is datetime.time:
            self.log(switch + " time: " + offtime.strftime("%H:%M:%S") + " day: " + constraindays + " off")
            self.run_daily(self.set_lights_off,offtime,constrain_days=constraindays,switch=switch)            

but even then it is still not very simple :wink:

            if "sunset" in splitline[1]:
              sunsetparts =splitline[1].split(":")
              offset1 = int(sunsetparts[1])
              sunseton = True
            elif "sunrise" in splitline[1]:
              sunsetparts =splitline[1].split(":")
              offset1 = int(sunsetparts[1])
              sunriseon = True
            else:
              ontime = self.parse_time(splitline[1])             

sunsetparts is that one of these strings? “sunset - 0:59:55”? Because if you wrote it like “sunset-0:59:55” you could use it directly as

if "sunset" in splitline[1] or "sunrise" in splitline[1]:
    self.log("running once")
    run_once(callback, self.parse_time(splitline[1]), switch=switch)
else:
    self.log("running daily")
    run_daily(callback, self.parse_time(splitline[1]), switch=switch)

And you would remove 40 lines or so :slight_smile:

I created a separate post with my implementation here: AppDaemon light schedule with sensor override

I’ve tried to segment the code for readability and avoid duplicate behaviour.

sorry, NO.
you need run_at_sunset. and not run_once or run_daily
and run at sunset needs offset seperatly

run_once is obsolete it this at all
run_daily will run at the set time from the moment you init. so it wil only 1 day be at the correct time.
only run_at_sunset will run at the right time every day.

Aah, I get it. But isn’t run_at_sunset/sunrise a one shot timer? Is it a recurring timer? I couldn’t see that in the documentation.

run_at_sunset listens to the sun entity from hass.

so run at sunset is actualy a listen_state(callback, “sun.sunset”)

Now I understand, and I see that I will need to change that for myself as well

does this support “random +/- 15 mins”?

no it doesnt.
but it can be implemented.

I’m curious why you’re not using run_at_sunrise/sunset… Do these only run once, maybe this is why you are manually adjusting for them?

Or will they run each rise/set once scheduled?

EDIT: I see your comment above which leads me to think I may give that method a go.

1 Like

It’s actually implemented by AppDaemon’s scheduler (although it was derived from HASS in the first version, I reimplemented it to native functionality to solve some race conditions). That said, the code is identical to HASS for calculating sunrise and sunset for consistency.

when you change things without telling me first i cant keep up to date, cant i? :stuck_out_tongue:
but thanks for claring things up. :wink:

Hehe - sorry, I will keep you better informed in future :wink:

1 Like

@ReneTode Just implemented this…my first appdaemon application. Now I can go clean up my automations in HASS to simplify. Would like to see a couple additions. :wink:

  1. Add random so it can start or stop from time + random minutes.
  2. If the entity is a light then allow to set dimmer %. Would allow you to gradually adjust the light.

Couple questions:

  1. If I change the settingsfile, I assume I need to reload the component so it picks up the changes. Also if it picks up the new schedule what happens to the old schedule? Does the switch I remove or update have two schedules?
  2. I noticed you use input_boolean. I assume that you have some other automation that is triggered off of those?

Thank you. Would really like to see more of your examples. (Specifically the dynamic sensor creator).

i really dont understand why people like to change their lights at random times. :wink:
it could be implemented but i like you to give that a try yourself. :wink:

setting dimmer percentage in an automation would make sense in combination with sunset or a lightsensor i guess, but i have no way to test that out, so i am sorry i cant give you that.

yeah, you need to restart the app. and for that you have 3 options right now:

  1. restart appdaemon
  2. edit the app (add a comment with the date from your file update for instance)
  3. edit the cfg (make an entry in the light app section with the lastchanged date for instance)

in all 3 cases all old schedules are removed.

i use(d) input_booleans for 433 mhz switches with a mysensors remote.
now they are switches because i made a custom component from it.

and last: i have no idea what you mean with dynamic sensor creator.

Thanks for following up. Random is so I can use it for when traveling and want to deviate from a set schedule. I think I have a few ideas on how to build on your code to add it. I’ll give it a try. I also want to add overrides so I can pause a schedule if needed.

The “dynamic sensor” is a reference to a post you had in the appdaemon Q&A thread. I was wondering if it is possible to use appdaemon to create entities on the fly and configure them without putting them in HA config files.

if you normally have a set schedule running and set a random when you are traveling, you tell everyone that you are not home :wink:
but if it is for that case you can add

random_start = -300, random_end = 300

to the run_at_sunrise, run_at_sunset and run_daily lines.
you could also make an input boolean in hass, which sets random on and on.
in that case use get_state to know if it is on and if it is on set the value to a specific amount, else to 1 (i dont know if random 0 would give a problem, thats why i say 1)

i didnt implement overrides because you can use callback constraints inputboolean in the cfg file.

yeah you can create an entity on the fly.
and you can change that value anytime you like.
just use setstate(“your_own_domain.your_sensor_name”)
the first time it will be created and after that updated.
i havent been able to create attributes for it though.
and setting states from existing entities still isnt working like expected.