Cleaning up first Appdaemon code

Hi Guys,

I just started using Appdaemon and its amazing, I have some minor Python expereince so please be kind with me:

I have the following code working:


import appdaemon.plugins.hass.hassapi as hass
class current_scene(hass.Hass):

    def initialize(self):

        self.handles = {}
        self.wakeup = self.get_state("input_datetime.aufwachen")
        self.sleep = self.get_state("input_datetime.schlafen")

        self.handle = self.listen_state(self.wakeup_changed,"input_datetime.aufwachen")
        self.handles["wakeup_changed"] =self.handle
        self.handle = self.listen_state(self.sleep_changed,"input_datetime.schlafen")
        self.handles["sleep_changed"] =self.handle
        self.handle = self.run_daily(self.wakeuptime, self.wakeup)
        self.handles["wakeuptime"] =self.handle
        self.handle = self.run_daily(self.sleeptime, self.sleep)
        self.handles["sleeptime"] =self.handle

        
        
        self.log("Handles have been created: {}".format(self.handles), level = "INFO")
        self.log("Wakeup time is: {}, Sleep time is: {}".format(self.wakeup, self.sleep), level = "INFO")


    def wakeuptime(self, kwargs):

        if self.get_state("input_boolean.manual_scene_mode") == "off":
            self.change_scene("Aufwachen")

        else:
            pass

    def sleeptime(self, kwargs):
        if self.get_state("input_boolean.manual_scene_mode") == "off":
            self.change_scene("Schlafen")

        else:
            pass


    def wakeup_changed(self, entity, attribute, old, new, kwargs):
        self.cancel_timer(self.handles["wakeuptime"])
        self.wakeup = self.get_state("input_datetime.aufwachen")
        self.handle = self.run_daily(self.wakeuptime, self.wakeup)
        self.handles["wakeuptime"] =self.handle
        self.log("Handles have been updated: {}".format(self.handles), level = "INFO")
        self.log("New wakeup time is: {}".format(self.wakeup), level = "INFO")

    def sleep_changed(self, entity, attribute, old, new, kwargs):
        
      self.cancel_timer(self.handles["sleeptime"])
      self.sleep = self.get_state("input_datetime.schlafen")
      self.handle = self.run_daily(self.sleeptime, self.sleep)
      self.handles["sleeptime"] =self.handle
      self.log("Handles have been updated: {}".format(self.handles), level = "INFO")
      self.log("New sleep time is: {}".format(self.sleep), level = "INFO")

         

So far so good. I know wanted to clean up the code a bit and replace some redundant parts by creating a function. This would handle the recreation of the run_daily callbacks when somebody picks a new sleep or wakeup time in the ui.

So i changed sleep_changed function and created a time_changed function as follows:

     

def sleep_changed(self, entity, attribute, old, new, kwargs):
        self.time_changed(handle_key ="sleeptime", newtime="input_datetime.schlafen", callbfunc ="self.sleeptime")




    def time_changed(self, handle_key, newtime, callbfunc):
        
        self.cancel_timer(self.handles[handle_key])
        newtime = self.get_state(newtime)
        self.handle = self.run_daily(callbfunc, newtime)
        self.handles[handle_key] =self.handle
        self.log("Handles have been updated: {}".format(self.handles), level = "INFO")
        self.log("New {} is: {}".format(handle_key, time), level = "INFO")
        

I cant get that to work i get the following error:

2020-11-19 16:26:05.809542 WARNING Szenen: ------------------------------------------------------------
2020-11-19 16:26:05.808783 WARNING Szenen: Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/appdaemon/threading.py", line 900, in worker
    funcref(
  File "/config/appdaemon/apps/scene.py", line 64, in sleep_changed
    self.time_changed(handle_key ="sleeptime", newtime="input_datetime.schlafen", callbfunc ="self.sleeptime")
  File "/config/appdaemon/apps/scene.py", line 91, in time_changed
    self.handle = self.run_daily(callbfunc, newtime)
  File "/usr/lib/python3.8/site-packages/appdaemon/utils.py", line 195, in inner_sync_wrapper
    f = run_coroutine_threadsafe(self, coro(self, *args, **kwargs))
  File "/usr/lib/python3.8/site-packages/appdaemon/utils.py", line 299, in run_coroutine_threadsafe
    result = future.result(self.AD.internal_function_timeout)
  File "/usr/lib/python3.8/concurrent/futures/_base.py", line 439, in result
    return self.__get_result()
  File "/usr/lib/python3.8/concurrent/futures/_base.py", line 388, in __get_result
    raise self._exception
  File "/usr/lib/python3.8/site-packages/appdaemon/adapi.py", line 2388, in run_daily
    handle = await self.run_every(callback, event, 24 * 60 * 60, **kwargs)
  File "/usr/lib/python3.8/site-packages/appdaemon/adapi.py", line 2563, in run_every
    handle = await self.AD.sched.insert_schedule(
  File "/usr/lib/python3.8/site-packages/appdaemon/scheduler.py", line 328, in insert_schedule
    function_name = callback.__name__
AttributeError: 'str' object has no attribute '__name__'

2020-11-19 16:26:05.807512 WARNING Szenen: ------------------------------------------------------------
2020-11-19 16:26:05.806750 WARNING Szenen: Worker Ags: {'id': 'ca8d858959b8475f92de0b89a6256dfb', 'name': 'Szenen', 'objectid': 'b5c239f8a07c4463ad2fc74572e199a2', 'type': 'state', 'function': >, 'attribute': 'state', 'entity': 'input_datetime.schlafen', 'new_state': '23:00:00', 'old_state': '22:00:00', 'pin_app': True, 'pin_thread': 2, 'kwargs': {'__thread_id': 'thread-2'}}
```


Any help is really appreciated

You passed a string for callbackfcn, then tried to use a string as a function.

No need to set self.handle, then move that to the map. Just set the map directly.

    def initialize(self):

        self.handles = {}
        self.wakeup = self.get_state("input_datetime.aufwachen")
        self.sleep = self.get_state("input_datetime.schlafen")

        self.handles["wakeup_changed"] = self.listen_state(self.wakeup_changed,"input_datetime.aufwachen")
        self.handles["sleep_changed"] = self.listen_state(self.sleep_changed,"input_datetime.schlafen")
        self.handles["wakeuptime"] = self.run_daily(self.wakeuptime, self.wakeup)
        self.handles["sleeptime"] = self.run_daily(self.sleeptime, self.sleep)

as for the actual problem, just remove the quotes from the callbfunc.

    def sleep_changed(self, entity, attribute, old, new, kwargs):
        self.time_changed(handle_key ="sleeptime", newtime="input_datetime.schlafen", callbfunc =self.sleeptime)

    def time_changed(self, handle_key, newtime, callbfunc):        
        self.cancel_timer(self.handles[handle_key])
        newtime = self.get_state(newtime)
        self.handles[handle_key] = self.run_daily(callbfunc, newtime)
        self.log("Handles have been updated: {}".format(self.handles), level = "INFO")
        self.log("New {} is: {}".format(handle_key, time), level = "INFO")
2 Likes

Thanks the first one was a bummer :slight_smile:

For the actual problem I’m happy that I wasn’t to far away. So basically that was what the line

AttributeError: 'str' object has no attribute '__name__'

was referring to correct? Instead of providing an object i provided a String?

Thats what i now came up with:



#App that automatically transitions through global scene states depening on the set wakeup and sleeptime

import appdaemon.plugins.hass.hassapi as hass

class current_scene(hass.Hass):

    def initialize(self):

# Initallize empty handle dict and get current wakeup and sleeptime
        self.handles = {}
        self.wakeup = self.get_state("input_datetime.aufwachen")
        self.sleep = self.get_state("input_datetime.schlafen")

# Create state and daily callbacks for wakeup changed, sleep changed and scene changed, wakeuptime, sleeptime
        self.handles["wakeup_changed"] = self.listen_state(self.wakeup_changed,"input_datetime.aufwachen")
        self.handles["sleep_changed"] = self.listen_state(self.sleep_changed,"input_datetime.schlafen")
        self.handles["scene_changed"] = self.listen_state(self.scene_changed,"input_select.scene_global")
        self.handles["wakeuptime"] = self.run_daily(self.wakeuptime, self.wakeup)
        self.handles["sleeptime"] = self.run_daily(self.sleeptime, self.sleep)

# Log Handles and current Wakeup/sleep time         
        self.log("Handles have been created: {}".format(self.handles), level = "INFO")
        self.log("Wakeup time is: {}, Sleep time is: {}".format(self.wakeup, self.sleep), level = "INFO")


##############################################################################################################
##############################################################################################################
#                            callback functions
##############################################################################################################
##############################################################################################################

# Called when wakeuptime is reached 
    def wakeuptime(self, kwargs):
        if self.get_state("input_boolean.manual_scene_mode") == "off":
            self.change_scene("Aufwachen")

# called when sleeptime is reached
    def sleeptime(self, kwargs):
        if self.get_state("input_boolean.manual_scene_mode") == "off":
            self.change_scene("Schlafen")

# called when wakeup time changes
    def wakeup_changed(self, entity, attribute, old, new, kwargs):
        self.time_changed(handle_key ="wakeuptime", newtime="input_datetime.aufwachen", callbfunc =self.wakeuptime)

#called when sleep time changes
    def sleep_changed(self, entity, attribute, old, new, kwargs):
        self.time_changed(handle_key ="sleeptime", newtime="input_datetime.schlafen", callbfunc =self.sleeptime)

# called whenever a new scene is set via ui. Disabled while timer callabcks sets scene automatically
    def scene_changed(self, entity, attribute, old, new, kwargs):
        self.turn_on("input_boolean.manual_scene_mode")
        self.log("Global Scene switched to {}  Manual mode is {}".format(new, self.get_state("input_boolean.manual_scene_mode")), level = "INFO")



##############################################################################################################
##############################################################################################################
#                                        Functions
##############################################################################################################
##############################################################################################################



# This function is used by the callbackfunctions sleeptime and wakeuptime when the respective time is reached to :
#1. cancel the scene_changed callback to prevent manual mode from turning on
#2. Set the newscene corresponding to the newscene ARG
#3. recreate the scene changed callback
#4. log updated handles, global scene mode and manual mode state
    def change_scene(self, newscene):
        self.cancel_listen_state(self.handles["scene_changed"])
        self.call_service("input_select/select_option",  entity_id = "input_select.scene_global", option = newscene)
        self.handles["scene_changed"] = self.listen_state(self.scene_changed,"input_select.scene_global")
        self.log("Handles have been updated: {}".format(self.handles), level = "INFO")
        self.log("Global Scene switched to {}  Manual mode is {}".format(newscene, self.get_state("input_boolean.manual_scene_mode")), level = "INFO")

# This function is used by the wakeup/ sleep_changed functions to:
#1. cancel the current timer handle with ARG handle_key
#2. get the new set time with ARG newtime
#3. Set a new timer call back with ARGs callbfunc and newtime and save new handle into seld. handles with ARG handle_key
#4.  Log new Handles and new time set
    def time_changed(self, handle_key, newtime, callbfunc):
        
        self.cancel_timer(self.handles[handle_key])
        newtime = self.get_state(newtime)
        self.handles[handle_key] = self.run_daily(callbfunc, newtime)
        self.log("Handles have been updated: {}".format(self.handles), level = "INFO")
        self.log("New {} is: {}".format(handle_key, newtime), level = "INFO")
    

Looks okay to me.

Could probably use callback constraints instead of the two if statements but I prefer to have everything in the py file instead of using constrains in the yaml file

Is this generally an okay way of achieving what i want or am i overcomplicating things?

Basically want to have my scene preset changing depending on the time of day but times need to be configurable via ui. I also have a manual mode to stop automatic progression.

1 Like

I’ve never written an appdaemon thing yet. Really want to check it out though as I’m decent with python…

I feel like you could do the same thing with just a normal automation, but that would require you to get good with YAML templates which, to me, was a pain to figure out at first. :stuck_out_tongue:. Plus, I think all of our firsts (automations, appdaemons, ect) are overcomplicated because we haven’t learned enough yet lol. I’d say just check the CPU usage of it and if it doesn’t seem high and it’s working, then it’s good enough for now!

The AttributeError, yes, was complaining that a string was trying to be used as an object. I couldn’t tell exactly what the issue was from there, but wasn’t hard to figure out with the context.

Yeah, exactly the reason why I want to use AppDaemon. I want to get better in Python as I can use it at work now and then and it makes more sense to use something more universal if you ever wish to maybe change to a different platform

Thanks anyway. And looking forward to seeing some of your Appdaemon Apps in the future :slight_smile: