Hi All, this release contains a fix for a security issue in a 3rd party library, no changes to anything else.
Hi thanks for the update.
Do you have any roadmap for AppDaemon 4.0?
How do I determine which version of AppDaemon I am running? I am currently running in a docker container and the docker file is appended with the “latest” tag. Any ideas?
You can check the currently installed version at this location
/usr/local/lib/python3.6/site-packages
inside your docker container.
I run an appdaemon app that runs daily and updates a sensor with the currently installed version. Find below if you are interested.
def set_sensor(self, kwargs: dict) -> None:
"""Set the sensor to the current installed version."""
subdirs = os.listdir("/usr/local/lib/python3.6/site-packages")
for subdirname in subdirs:
if "appdaemon" in subdirname.lower():
if "info" in subdirname.lower():
version = subdirname.split("-")[1][:-5]
self.set_state("sensor.appdaemon_installed",
state=version)
drwxr-sr-x 1 root staff 258 Dec 9 13:26 appdaemon
drwxr-sr-x 1 root staff 134 Dec 9 13:26 appdaemon-3.0.2.dist-info
does this indicate I am running version 3.0.2? If so where can I find info about benefits of upgrading to the latest? I ask because it seems that very rarely a time based automation fails to fire.
Nothing big changed since 3.0.2 you can find all the changes here
https://appdaemon.readthedocs.io/en/latest/HISTORY.html
Post your time based automation problem in a new topic and maybe someone might be able to identify an error in your code.
One final question, maybe. Is there a way to use multiple loggers to segment items into separate logger files? I’m hoping this will lead to an answer with the time based automatons. Also can you offer formatting suggestions (right now there is no entry separation) thanks.
I don’t know if there is a build in way for this, but you could simply write the log to a file instead of the log. Why do you need this? Do you have so many log items in your log?
What do you mean with formatting suggestions?
I am trying to use the log file in the place of a print statement, trying document code execution to determine exactly when a function is being executed, so that a can determine what programming errors I may have. Is there a way to create new line characters in the log “\n” or something like that?
Sorry, I don’t get it. Can you please show your code and show where you want to log?
I followed the docker example when I first got AppDaemon running in that example it demonstrated a file system containing a appdaemon/conf with error.log and appdaemon.log in the conf directory. So I have been using that all along (using self.log to put all the items in one long log). Below is a segment of code I have been using. I am very novice and am open to any suggestions you might have. Thank you
import appdaemon.plugins.hass.hassapi as hass
import datetime
#
# App to control Pool Main Pump
#
# Args: input_datetime for start and stop times
#
class poolPumpMain(hass.Hass):
#required function -- must initialize variables
def initialize(self):
#this var is to detect wheather or not a timer / scheduler is active
self.pool_main_pump_on_timer = None
self.pool_main_pump_off_timer = None
startTime = self.get_state(self.args["startTime"])
startTimeHour = int(startTime[0:2])
startTimeMinute = int(startTime[3:5])
stopTime = self.get_state(self.args["stopTime"])
stopTimeHour = int(stopTime[0:2])
stopTimeMinute = int(stopTime[3:5])
on_time = datetime.time(startTimeHour, startTimeMinute, 0)
off_time = datetime.time(stopTimeHour, stopTimeMinute, 0)
self.run_daily(self.on_time_cb, on_time)
self.run_daily(self.off_time_cb, off_time)
#this detects if there has been a change to entity in Home Assistant, and calls the callback when a change has been detected
self.handle = self.listen_state(self.startTimeChangedCallBack, "input_datetime.pool_pump_start_time")
self.handle = self.listen_state(self.stopTimeChangedCallBack, "input_datetime.pool_pump_stop_time")
self.handle = self.listen_state(self.pump_on_cb_via_24_hour_run, "input_boolean.pool_pump_main_24_hour_run", new = "on")
self.handle = self.listen_state(self.pump_on_cb_via_man_start, "input_boolean.pool_pump_main_start", new = "on")
self.handle = self.listen_state(self.pump_on_cb_via_time, "input_number.pool_pump_main_run_daily_int", new = "1")
self.handle = self.listen_state(self.pump_on_cb_low_temperature, "input_boolean.low_outside_temperature", new = "on")
self.handle = self.listen_state(self.pump_off_via_man_stop_cb, "input_boolean.pool_pump_main_stop", new = "on")
self.handle = self.listen_state(self.pump_off_via_time_cb, "input_number.pool_pump_main_run_daily_int", new = "0")
#this is the main callback to start the pool pump
#the input_number.pool_pump_main_run_daily_int is set to "2" at the end of the function to ensure there is a change
#when the on_time_cb or the off_time_cb is called. the on_time_cb and off_time_cb are the function which starts
#and stops the pump based on time. "0" stops the pump and "1" starts the pump
def pump_on_cb_via_24_hour_run(self, entity, attribute, old, new, kwargs):
self.log("Entering pump_on_cb via 24 hour run")
self.turn_on("switch.pool_main_pump")
self.log("MQTT command sent to turn on Main Pool Pump")
self.set_state("input_number.pool_pump_main_run_daily_int", state = 2)
def pump_on_cb_via_man_start(self, entity, attribute, old, new, kwargs):
self.log("Entering pump_on_cb via manual start")
self.turn_on("switch.pool_main_pump")
self.log("MQTT command sent to turn on Main Pool Pump")
self.set_state("input_number.pool_pump_main_run_daily_int", state = 2)
def pump_on_cb_via_time(self, entity, attribute, old, new, kwargs):
self.log("Entering pump_on_cb via time")
self.turn_on("switch.pool_main_pump")
self.log("MQTT command sent to turn on Main Pool Pump")
self.set_state("input_number.pool_pump_main_run_daily_int", state = 2)
def pump_on_cb_low_temperature(self, entity, attribute, old, new, kwargs):
self.log("Entering pump_on_cb via low temperature")
self.turn_on("switch.pool_main_pump")
self.log("MQTT command sent to turn on Main Pool Pump")
self.set_state("input_number.pool_pump_main_run_daily_int", state = 2)
#this is the main callback to stop the pool pump
#the input_number.pool_pump_main_run_daily_int is set to "2" at the end of the function to ensure there is a change
#when the on_time_cb or the off_time_cb is called. the on_time_cb and off_time_cb are the function which starts
#and stops the pump based on time. "0" stops the pump and "1" starts the pump
def pump_off_via_man_stop_cb(self, entity, attribute, old, new, kwargs):
self.log("Entering pump_off_cb")
lowTemp = self.get_state("input_boolean.low_outside_temperature") #check to see if there is a danger of freezing
poolRun24Hour = self.get_state("input_boolean.pool_pump_main_24_hour_run") #check to see if the 24hour run command has been issued
self.set_state("input_number.pool_pump_main_run_daily_int", state = 2) #reset sentinal vaule
if ((lowTemp == "off") and (poolRun24Hour == "off")): #if there is no danger of freezing and the 24hour run
self.turn_off("switch.pool_main_pump") #command are both off allow the pump to be stopped
self.log("MQTT command sent to turn off Main Pool Pump via the manual stop command")
def pump_off_via_time_cb(self, entity, attribute, old, new, kwargs):
self.log("Entering pump_off_cb")
lowTemp = self.get_state("input_boolean.low_outside_temperature") #check to see if there is a danger of freezing
poolRun24Hour = self.get_state("input_boolean.pool_pump_main_24_hour_run") #check to see if the 24hour run command has been issued
self.set_state("input_number.pool_pump_main_run_daily_int", state = 2) #reset sentinal vaule
if ((lowTemp == "off") and (poolRun24Hour == "off")): #if there is no danger of freezing and the 24hour run
self.turn_off("switch.pool_main_pump") #command are both off allow the pump to be stopped
self.log("MQTT command sent to turn off Main Pool Pump via time")
def on_time_cb(self, kwargs):
# function to set a value to call the pump_on_cb
self.set_state("input_number.pool_pump_main_run_daily_int", state = 1)
self.log("on_time_cb called")
def off_time_cb(self, kwargs): # function to set a value to call the pump_off_cb
self.set_state("input_number.pool_pump_main_run_daily_int", state = 0) #this function simply change the sentinal value to
self.log("off time callback called") #to 0 causing the pump to stop
def startTimeChangedCallBack(self, entity, attribute, old, new, kwargs):
#Slice the hour and minute out of the start time "the new variable"
startTimeHour = int(new[0:2])
startTimeMinute = int(new[3:5])
#set the on_time variable, to be used for starting the pump based on schedule
on_time = datetime.time(startTimeHour, startTimeMinute, 0)
self.log("on time = {}".format(on_time))
if self.pool_main_pump_on_timer != None:
self.cancel_timer(self.pool_main_pump_on_timer)
self.pool_main_pump_on_timer = self.run_daily(self.on_time_cb, on_time)
self.log("run daily on time callback")
def stopTimeChangedCallBack(self, entity, attribute, old, new, kwargs):
#Slice the hour and minute out of the stop time "the new variable"
stopTimeHour = int(new[0:2])
stopTimeMinute = int(new[3:5])
#set the off_time variable, to be used for stopping the pump based on schedule
off_time = datetime.time(stopTimeHour, stopTimeMinute, 0)
self.log("off time = {}".format(off_time))
if self.pool_main_pump_off_timer != None:
self.cancel_timer(self.pool_main_pump_off_timer)
self.pool_main_pump_off_timer = self.run_daily(self.off_time_cb, off_time)
self.log("run daily off time callback")
And where do you get a problem? I found an error, the two callbacks you run daily
self.run_daily(self.on_time_cb, on_time) self.run_daily(self.off_time_cb, off_time)
get never cancelled when the pool start/end time changes. You need to have them like this:
self.pool_main_pump_on_timer = self.run_daily(self.on_time_cb, on_time)
self.pool_main_pump_off_timer = self.run_daily(self.off_time_cb, off_time)
this way the will get cancelled in your startTimeChangedCallBack
and stopTimeChangedCallBack
callback.
I have some suggestions for your coding:
- try to limit lines to 79 characters, makes code easier to read
- you do some slicing and int conversion and datetime just to convert a simple date, use the built in
self.parse_time
this way you can have your time in the yaml file like this “11:30:00” - you don’t need and use all the handles you define for the state listeners, just write
self.listen_state
instead ofself.handle = self.listen_state
- you have lots of redundant code in your callbacks, better have one callback function that distinguishes the way it has been entered or create an additional function that turns on the pool pump, logs the MQTT thing and sets the input number
- the same redundancy in the pump off callbacks, you can put the check if low_temp == off and poolRun24Hour == off into a separate function
- this
if self.pool_main_pump_on_timer != None:
should beif self.pool_main_pump_on_timer is not None:
orif self.pool_main_pump_on_timer:
- function names should be lower case:
startTimeChangedCallBack
andstopTimeChangedCallBack
I suggest to use an IDE for developing apps like PyCharm, which will autmaticall show some of the suggestions I made above.
thank you for your suggestions. However I’m not sure I understand your first two comments, where you found the error. I have the
self.pool_main_pump_on_timer = self.run_daily(self.on_time_cb, on_time)
self.pool_main_pump_off_timer = self.run_daily(self.off_time_cb, off_time)
near the end of the file in separate callbacks. Have I done this incorrectly?
Also the reason I ended up with all the redundant callbacks was due to my lack of understanding based on variable being passed into the various functions, especially when monitoring for changes from HomeAssistant. I will look into this further, thank you.
Yes your way is incorrect, the way you are doing it you are have two callbacks that run daily which you define in the beginning and then another callback when you change the start/stop time. The cancel_timer in your callbacks doesn’t cancel the two callbacks you defined at the beginning and they will continue to run daily.
OK. Thank you