Appdaemon lightschedule

if you are starting to implement light automations there are a few.
and then over time it grows.
you add a new light and a new automation, and another automation, and …

listing all your automations beneath each other doesnt really give you a good view on things.
i had a view light apps running. and i reused them resulting in something like this in the cfg file:

[lichtuitbuiten1]
module = daily
class = lightsout
days = 1,2,3,4,5,6,7
uur = 1
minuut = 40
total_switches = 1
switch1 = input_boolean.waterloop

[lichtuitbuiten2]
module = daily
class = lightsout
uur = 1
minuut = 41
days = 1,2,3,4,5,6,7
total_switches = 1
switch1 = input_boolean.jacuzzi

[lichtuitkelder]
module = daily
class = lightsout
uur = 1
minuut = 30
days = 1,2,3,4,5,6,7
total_switches = 1
switch1 = input_boolean.aquariums

[lichtuitbinnen]
module = daily
class = lightsout
uur = 20
minuut = 25
days = 5,6
total_switches = 1
switch1 = input_boolean.allesuit
constrain_start_time = sunset - 01:00:00
constrain_end_time = sunrise

[lichtaanbinnen]
module = daily
class = lightson
uur = 0
minuut = 30
days = 6,7
total_switches = 7
switch1 = input_boolean.hboog
switch2 = input_boolean.hbank
switch3 = input_boolean.hhoek
switch4 = input_boolean.eraamhoek
switch5 = input_boolean.epchoek
switch6 = input_boolean.ewandmeubel
switch7 = switch.kachel_9_1
constrain_start_time = sunset
constrain_end_time = sunrise

[dekkiesuit]
module = daily
class = lightsout
days = 1,2,3,4,5,6,7
uur = 7
minuut = 00
total_switches = 1
switch1 = input_boolean.olindedekkie

[dekkieolindeaan]
module = daily
class = lightson
days = 1,2,3,4,7
uur = 23
minuut = 00
total_switches = 1
switch1 = input_boolean.olindedekkie

[dekkieolindeaanweekend]
module = daily
class = lightson
days = 6,7
uur = 0
minuut = 45
total_switches = 1
switch1 = input_boolean.olindedekkie

[lichtaanbuiten1]
module = sunset
class = sunsetlights
offset = 0
aanuit = autobuiten
total_switches = 1
switch1 = input_boolean.jacuzzi

[lichtaanbuiten2]
module = sunset
class = sunsetlights
offset = 60
aanuit = autobuiten
total_switches = 1
switch1 = input_boolean.waterloop

[lichtaanhuiskamer]
module = sunset
class = sunsetlights
offset = -3600
aanuit = autohuiskamer
total_switches = 4
switch1 = input_boolean.hboog
switch2 = input_boolean.hbank
switch3 = input_boolean.hhoek
switch4 = switch.kachel_9_1

[lichtaaneetkamer]
module = sunset
class = sunsetlights
offset = -3300
aanuit = autoeetkamer
total_switches = 3
switch1 = input_boolean.eraamhoek
switch2 = input_boolean.epchoek
switch3 = input_boolean.ewandmeubel

i dont know how you see it, but i lose the overview by reading that.
so i have made a new app. based on a .csv file which i can easy edit in excel (or another spreadsheet programm, or any editor)
and then it looks like this:

suddenly i have an good view again on when my lights go on and off.
you can easy add new lights or other switches to it, group things and rearrange things.

you wanna know about this app?
here it is:

###########################################################################################
#                                                                                         #
#  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(";")
        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]
          if type(ontime) is datetime.time:
            self.log(switch + " tijd: " + ontime.strftime("%H:%M:%S") + " dag: " + constraindays + " on")
            self.run_daily(self.set_lights_on,ontime,constrain_days=constraindays,switch=switch)
          if type(offtime) is datetime.time:
            self.log(switch + " tijd: " + offtime.strftime("%H:%M:%S") + " dag: " + 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"])

and all you need in your cfg file is:

[verlichting]
module = lights
class = switches
settingsfile = C:\Users\rene\AppData\Roaming\.homeassistant\lichtschema.csv

hope you enjoy this

edit: changed dutch in app to english

1 Like

I like this approach but I’m getting an error reading the file and I’m not sure why - any ideas?

EDIT:

[lighting]
module = lights
class = switches
settingsfile = /config/apps/settings_files/light_schedule.csv

I was able to find the read error but it doesn’t seem to be splitting the rows correctly. is the splitline = line.split(";") correct?

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).