Just to close the loop: Followed the instructions per @Burningstone, worked great. I ended up re-writing all the core logic to ingest a list of control dictionaries, but it’s much cleaner now, and more easily extensible. I’ll paste the full code below (names changed).
Now I have a fully functioning control panel for my bedtime automation, which immediately updates AD upon changes in HA.
I added a few other bells-and-whistles, like input_booleans to toggle on/off particular kids (if, for instance, one is sick and I don’t want the bedtime routine to bother them) and a master-automation kill-switch. I think the only other FYI is that I have my AlexaSay() procedure as a service within HomeAssistant’s Python Script framework, so I can call it aaS from where-ever. I also renamed my input_datetime and input_select entities with consistent names, so I could easily drive them with the c[‘name’] control record.
Thanks again @Burningstone for the point in the right direction!
import appdaemon.plugins.hass.hassapi as hass
import datetime as dt
class kids_bedtime(hass.Hass):
def initialize(self):
self.prefix = "Attention bert and ernie! "*2
self.ctl = [ {'name':'bedtime1_go_upstairs', 'phase':'0', 'subsec':'0', 'kid':'both', 'time':'', 'echo':'', 'def':'bedtime1_go_upstairs' , 'handle':'' }
,{'name':'bedtime2_get_ready', 'phase':'0', 'subsec':'0', 'kid':'both', 'time':'', 'echo':'', 'def':'bedtime2_get_ready' , 'handle':'' }
,{'name':'bedtime3_ready_for_bed', 'phase':'0', 'subsec':'0', 'kid':'both', 'time':'', 'echo':'', 'def':'bedtime3_ready_for_bed' , 'handle':'' }
,{'name':'bedtime4_read_books', 'phase':'0', 'subsec':'0', 'kid':'both', 'time':'', 'echo':'', 'def':'bedtime4_read_books' , 'handle':'' }
,{'name':'bedtime5_lights_out_bert', 'phase':'1', 'subsec':'180', 'kid':'bert', 'time':'', 'echo':'', 'def':'bedtime5_lights_out' , 'handle':'' }
,{'name':'bedtime5_lights_out_ernie', 'phase':'1', 'subsec':'180', 'kid':'ernie', 'time':'', 'echo':'', 'def':'bedtime5_lights_out' , 'handle':'' }
,{'name':'bedtime5_lights_out_bert', 'phase':'2', 'subsec':'20', 'kid':'bert', 'time':'', 'echo':'', 'def':'bedtime5_lights_out' , 'handle':'' }
,{'name':'bedtime5_lights_out_ernie', 'phase':'2', 'subsec':'20', 'kid':'ernie', 'time':'', 'echo':'', 'def':'bedtime5_lights_out' , 'handle':'' }
,{'name':'bedtime5_lights_out_bert', 'phase':'3', 'subsec':'0', 'kid':'bert', 'time':'', 'echo':'', 'def':'bedtime5_lights_out' , 'handle':'' }
,{'name':'bedtime5_lights_out_ernie', 'phase':'3', 'subsec':'0', 'kid':'ernie', 'time':'', 'echo':'', 'def':'bedtime5_lights_out' , 'handle':'' }
]
for c in self.ctl:
self.log("-INITIALIZING: %s for %s (phase %s)" %(c['name'], c['kid'], c['phase']))
self.load_callback(c)
# listeners for changes in input_select and input_datetime controls:
self.listen_state(self.change_detected, 'input_select.%s_echo' %c['name'], ctl=c)
self.listen_state(self.change_detected, 'input_datetime.%s_time' %c['name'], ctl=c)
# ----------------------------------------------
# HELPER FUNCTIONS
# ----------------------------------------------
def change_detected(self, entity, attributes, old, new, kwargs):
if new is not None and new is not old:
self.log("-CHANGE DETECTED: %s" %entity)
c = kwargs['ctl']
self.cancel_timer(c['handle'])
self.load_callback(c)
def load_callback (self, ctl):
c = ctl
# pull time and echo, and update the control record
tm = self.get_state("input_datetime.%s_time" %c['name'] )
c['time'] = dt.datetime.strptime(tm, "%H:%M:%S").time()
c['echo'] = self.get_state("input_select.%s_echo" %c['name'] )[:1].lower()
self.log("---Loading control dictionary: %s" %c)
c['handle'] = self.run_daily(getattr(self,c['def']), self.subtractSecs(c['time'],c['subsec']), ctl=c )
self.log("---Initialized with Handler: %s" %c)
def subtractSecs(self, tm, secs=0):
import datetime as dt
if secs==0:
return tm
else:
fulldate = dt.datetime(100, 1, 1, tm.hour, tm.minute, tm.second)
fulldate = fulldate - dt.timedelta(seconds=int(secs))
return fulldate.time()
def get_skip(self):
skip = ''
if self.get_state("input_boolean.bedtime_active_bert")=='off': skip = skip + 'b'
if self.get_state("input_boolean.bedtime_active_ernie")=='off': skip = skip + 'e'
return skip
def automations_active(self):
return self.get_state("input_boolean.automations_active")=='on'
# ----------------------------------------------
# ANNOUNCEMENTS AND OTHER LIGHT AUTOMATIONS
# ----------------------------------------------
def bedtime1_go_upstairs (self, kwargs):
if self.automations_active():
msg = self.prefix + "Time to get ready for bed. Say goodnight to Oompa and head upstairs to your rooms."
c = kwargs['ctl']
self.log("--Announcing: %s \n---%s" %(msg,c))
self.call_service("python_script/alexa_say", where=c['echo'], msg=msg, skip=self.get_skip())
self.turn_on("light.zooz_zen22_dimmer_v2_level_2", brightness=70) # stairs light
def bedtime2_get_ready (self, kwargs):
if self.automations_active():
msg = self.prefix + "You should be in your rooms, getting ready for bed. bert, please take a shower. ernie, shower if you need, otherwise please get your pajamas on, comb hair and brush teeth."
c = kwargs['ctl']
skip = self.get_skip()
self.log("--Announcing: %s \n---%s" %(msg,c))
self.call_service("python_script/alexa_say", where=c['echo'], msg=msg, skip=skip)
if 'b' not in skip:
self.turn_on("switch.berts_light")
if 'e' not in skip:
self.turn_on("switch.ernies_light")
def bedtime3_ready_for_bed (self, kwargs):
if self.automations_active():
msg = self.prefix + "You should be ready for bed now, with your pajamas on and teeth brushed. Make sure you have your eyemask, grab a book and start heading to bed. Reading time starts in a few minutes."
c = kwargs['ctl']
skip = self.get_skip()
self.log("--Announcing: %s \n---%s" %(msg,c))
self.call_service("python_script/alexa_say", where=c['echo'], msg=msg, skip=skip)
if 'b' not in skip:
self.turn_on("switch.4336784084f3eb5d2039") # bert's bed light
self.turn_on("switch.4336784084f3eb5d6bae") # bert's sounds
if 'e' not in skip:
self.turn_on("switch.4336784084f3eb5d5bef") # ernie's sounds
self.turn_on("switch.4336784084f3eb5d14de") # ernie's bed light
self.turn_on("light.zooz_zen22_dimmer_v2_level_2", brightness=30) # stairs light
def bedtime4_read_books (self, kwargs):
if self.automations_active():
msg = self.prefix + "Time to read books. Snuggle into bed and get comfortable. Mom and Dad love you very much."
c = kwargs['ctl']
skip = self.get_skip()
self.log("--Announcing: %s \n---%s" %(msg,c))
self.call_service("python_script/alexa_say", where=c['echo'], msg=msg, skip=skip)
if 'b' not in skip:
self.turn_off("switch.berts_light")
self.turn_off("switch.berts_overhead_light")
if 'e' not in skip:
self.turn_off("switch.ernies_light")
self.turn_off("switch.ernies_overhead_light")
def bedtime5_lights_out (self, kwargs):
if self.automations_active():
c = kwargs['ctl']
skip = self.get_skip()
if c['phase'] == '1':
msg = "Lights out in 3 minutes. Find your bookmark and a good stopping point for the night."
self.log("--Announcing: %s \n---%s" %(msg,c))
self.call_service("python_script/alexa_say", where=c['echo'], msg=msg, skip=skip)
if 'b' not in skip:
self.turn_off("switch.berts_shower_light")
if 'e' not in skip:
self.turn_off("switch.ernies_shower_light")
if c['phase'] == '2':
msg = "Dear %s. Lights out in a few seconds. Place your bookmark and set down your book." %c['kid']
self.log("--Announcing: %s \n---%s" %(msg,c))
self.call_service("python_script/alexa_say", where=c['echo'], msg=msg, skip=skip)
if c['phase'] == '3':
msg = "Sweet %s. Time for lights out. Mom and Dad love you very much!" %c['kid']
self.log("--Announcing: %s \n---%s" %(msg,c))
self.call_service("python_script/alexa_say", where=c['echo'], msg=msg, skip=skip)
if c['kid'] == "bert" and 'b' not in skip:
self.turn_off("switch.4336784084f3eb5d2039") # bert's bed light
if c['kid'] == "ernie" and 'e' not in skip:
self.turn_off("switch.4336784084f3eb5d5bef") # ernie's bed light