I’m having some issues with at least two apps that, under certain “as of yet not completely known” conditions results in an endless loop of:
2022-06-03 15:18:11.948322 CRITICAL AppDaemon: Thread thread-5 has died
2022-06-03 15:18:11.949037 CRITICAL AppDaemon: Pinned apps were: ['car_charge_automation']
2022-06-03 15:18:11.949872 CRITICAL AppDaemon: Thread will be restarted
2022-06-03 15:18:11.950457 INFO AppDaemon: Adding thread 5
2022-06-03 15:18:11.951699 CRITICAL AppDaemon: Thread thread-19 has died
2022-06-03 15:18:11.952173 CRITICAL AppDaemon: Pinned apps were: ['dishwasher_finished']
2022-06-03 15:18:11.952677 CRITICAL AppDaemon: Thread will be restarted
2022-06-03 15:18:11.953380 INFO AppDaemon: Adding thread 19
With my (documented) difficulty understanding timers, I’m kind of suspecting that might be the culprit. I did see something similar in a different thread, but I don’t think the suggestion there is relevant for the first one (car_charge_automation
).
For the second (dishwasher_finished
), there was something suspiciously similar, namely:
self.power_timer = self.run_in(self.who_and_when(old=old, new=new), 900)
I have tried a few different ways of doing this now, but nothing so far that has worked for me.
I think they work; I get the expected notification, but then the loop starts, and I’m assuming they won’t work again while that’s happening?
AppDaemon script for "car_charge_automation"
import datetime
import hassapi as hass
import globals
class CarCharger(hass.Hass, globals.Entities, globals.Functions):
def initialize(self):
self.start_time = None
self.automate_off_timer = None
self.charge_time = 3 # Number of hours for full charge. Change with new car.
self.finish_time = 7 # What time in the morning to finish.
self.automate(None, None, None, None, None)
self.listen_state(self.automate, self.nordpool["current_price"], attribute = "tomorrow", old= 24 * [None])
self.listen_state(self.automate, self.nordpool["current_price"], attribute = "tomorrow", old= [])
self.listen_state(self.automate, self.automation_control["car_charger"], new="on")
self.listen_state(self.automate, self.edgeos["kia_niro"], new="on")
self.run_daily(self.automate(None, None, None, None, None), "22:00:00")
self.listen_event(self.charge_now, "CAR_CHARGER")
# self.listen_event(self.automate, "SET_CAR_CHARGER") # Covered by turning off, then on the boolean.
def automate(self, entity, attribute, old, new, kwargs):
"""
Determine whether the charger should be automated.
If yes, run scripts to determine when if the prices for tomorrow are
available.
Otherwise turn off, and the listen_state for that attribute will run
when it is populated by a list of prices (instead on [None]).
"""
if self.get_state(self.automation_control["car_charger"]) == "on":
on_time = self.determine_cheapest_time()
self.toggle_charge_state(on_time)
def determine_cheapest_time(self):
"""
Determine the best time to start charging.
There should probably be a limit that it won't do so before the nordpool
sensor has prices for tomorrow.
"""
today = self.get_entity(self.nordpool["current_price"]).get_state(attribute="today")
tomorrow = self.get_entity(self.nordpool["current_price"]).get_state(attribute="tomorrow")
now = self.get_state("sensor.time")
now_hour = int(now.split(":")[0])
today_from_now = today[now_hour:]
today_and_tomorrow = self.wait_for_tomorrow_prices(today, tomorrow, today_from_now)
today_datetime = datetime.date.today()
tomorrow_datetime = datetime.date.today() + datetime.timedelta(1)
if today_and_tomorrow != []: # Finds the index, t = i, corresponding to the start time/cost that yoields the cheapest 3 cosecutive hours ending before 07:00.
p = 100
t = None
for i in range(len(today_and_tomorrow) - (self.charge_time - 1)):
p1 = 0
for j in range(self.charge_time):
p1 += today_and_tomorrow[i + j]
if p1 < p:
p = p1
t = i # t is an index in the list of prices per hour starting at current time, and going until midnight if not tomorrow times available, or until self.finish_time if they are.
what_time = t + now_hour # get the hour of the day to start.
if what_time >= 24:
on_time = datetime.datetime.combine(tomorrow_datetime, datetime.time(what_time - 24, 0))
else:
on_time = datetime.datetime.combine(today_datetime, datetime.time(what_time, 0))
return on_time
def wait_for_tomorrow_prices(self, today, tomorrow, today_from_now):
"""
Determine whether to wait for tomorrows prices.
If plugging in after midninght, but before 07:00, don't wait.
"""
if self.now_is_between("00:00:00", "08:00:00"):
return today_from_now
elif len(tomorrow) == 24:
try:
float(tomorrow[1]) # Sometimes I've seen 24 * None
return today_from_now + tomorrow[:self.finish_time]
except:
self.turn_on(self.car_charger["sleep_mode"])
return []
else:
self.turn_on(self.car_charger["sleep_mode"])
return []
def toggle_charge_state(self, on_time):
connected_states = ["charging", "connected"]
now_datetime = self.get_now()
if self.get_state(self.car_charger["station_status"]) in connected_states:
if on_time >= datetime.datetime.now():
self.turn_on(self.car_charger["sleep_mode"])
data = {"tag": "car_charger_notification"}
self.call_service(self.notify_phones["walden"], message="Car Charger set for " + str(on_time), data=data)
if self.start_time: # Recommended here: https://community.home-assistant.io/t/cancel-timer-reporting-invalid-callback-handle/426416/2
if self.timer_running(self.start_time):
self.cancel_timer(self.start_time)
self.start_time = None
try:
self.start_time = self.run_at(self.start_charging, on_time) # Can do if event is not in the past.
except:
self.start_charging(None) # If on_time was in the past, just start now.
state, attributes = self.future_events()
try:
attributes["car_charging"] = on_time
except:
attributes = {"car_charging": "not set"}
attributes["car_charging"] = on_time
self.set_state("binary_sensor.future_events", state="on", attributes=attributes)
def start_charging(self, kwargs):
self.turn_off(self.car_charger["sleep_mode"])
def charge_now(self, event_name, data, kwargs):
if self.automate_off_timer: # Recommended here: https://community.home-assistant.io/t/cancel-timer-reporting-invalid-callback-handle/426416/2
if self.timer_running(self.automate_off_timer):
self.cancel_timer(self.automate_off_timer)
self.automate_off_timer = None
# if self.automate_off_timer != None:
# self.cancel_timer(self.automate_off_timer)
self.turn_off(self.automation_control["car_charger"])
self.turn_off(self.car_charger["sleep_mode"])
self.automate_off_timer = self.run_in(self.turn_on_automation, 10800)
def turn_on_automation(self):
self.turn_on(self.automation_control["car_charger"])
def notify_if_not_charging(self):
pass
AppDaemon script for "dishwasher_finished"
import hassapi as hass
import globals
class Appliances(hass.Hass, globals.Entities, globals.Functions):
def initialize(self):
self.notification_timer = None
self.power_timer = None
self.listen_state(self.timer_function, self.power_sensors["dishwasher"])
def timer_function(self, entity, attribute, old, new, kwargs):
"""
Check that the dishwasher power consumption is "low", and stays there upon next state update.
"""
if 20 > float(old) > 10 and float(new) < 5:
if self.power_timer == None:
self.power_timer = self.run_in(self.who_and_when(old=old, new=new), 900) #900
def who_and_when(self, **kwargs): #(self, old, new):
"""
Check whether the dishwasher is finished (dropping below 5 W).
Determine who is home to empty it.
"""
home = []
old = kwargs["old"]
new = kwargs["new"]
if self.get_state(self.device_trackers["emilie"]) == "home":
home.append("emilie")
if self.get_state(self.device_trackers["naia"]) == "home":
home.append("naia")
if self.get_state(self.device_trackers["kristina"]) == "home":
home.append("kristina")
if self.get_state(self.device_trackers["walden"]) == "home":
home.append("walden")
self.notify(home, old, new)
# if len(home) > 0:
# if self.awake():
# # self.notify(home, old, new)
# self.notify(home)
# else:
# today = self.get_entity(self.workday["actual"]).get_state(attribute = "workday_today") == "on"
# tomorrow = self.get_entity(self.workday["actual"]).get_state(attribute = "workday_tomorrow") == "on"
# if self.now_is_between("19:30:00", "00:00:00") and tomorrow:
# self.notification_timer = self.run_at(self.notify(home, old, new), "07:15:00")
# elif self.now_is_between("19:30:00", "00:00:00") and not tomorrow:
# self.notification_timer = self.run_at(self.notify(home, old, new), "10:00:00")
# elif self.now_is_between("00:00:00", "07:15:00") and today:
# self.notification_timer = self.run_at(self.notify(home, old, new), "07:15:00")
# elif self.now_is_between("00:00:00", "10:00:00") and not today:
# self.notification_timer = self.run_at(self.notify(home, old, new), "10:00:00")
def notify(self, home, old, new):
who = home[0]
notify_who = self.notify_phones[who]
kids = ["emilie", "naia", "luca"]
today = self.get_entity(self.workday["actual"]).get_state(attribute="workday_today") == "on"
tomorrow = self.get_entity(self.workday["actual"]).get_state(attribute="workday_tomorrow") == "on"
self.call_service(
self.notify_phones["walden"],
message = "Opvaskemaskinen er klar til at blive tømt. \nNotify_Who = " + str(notify_who) + ". \nHome = " + str(home) + ".\n old, new = " + str(old) + ", " + str(new)
)
# self.call_service(
# notify_who,
# message = "Opvaskemaskinen er klar til at blive tømt"
# )
#
# if home[0] in kids:
# if any((
# all(
# tomorrow, self.now_is_between("12:00:00", "20:30:00")
# ),
# all(
# today, self.now_is_between("08:00:00", "12:00:00")
# ),
# all(
# not tomorrow, self.now_is_between("12:00:00", "21:30:00")
# ),
# all(
# not today, self.now_is_between("10:00:00", "12:00:00")
# )
# )):
# self.speak()
title = "Opvaskemaskinen skal tømmes!"
message = ", opvaskemaskinen er klar til at blive tømt. Husk at starte den igen hvis du fylder den."
if len(home) > 0:
pass
# self.call_service(self.notify_phones[who], title=title, message=who.capitalize() + message)
else:
people_to_empty = ["naia", "emilie", "kristina", "walden"]
for device in people_to_empty:
self.someone_coming_home = self.listen_state(self.message_when_home, self.device_trackers[device])
# self.call_service(
# "notify/" + self.notify_devices["walden_phone"],
# message = "Opvaskemaskinen er klar til at blive tømt"
# )
def message_when_home(self, entity, attribute, old, new, kwargs):
title = "Opvaskemaskinen skal tømmes!"
message = ", opvaskemaskinen er klar til at blive tømt. Husk at starte den igen hvis du fylder den."
home = [key for key, val in self.device_trackers.items() if entity == val][0]
self.call_service(self.notify_phones[entity], title=title, message=home.capitalize() + message)
self.cancel_listen_state(self.someone_coming_home)
def speak(self):
self.call_service(
"tts/google_translate_say",
entity_id=self.speakers["top_floor_speaker"],
message="Opvaskemaskinen er klar til at blive tømt",
language="da"
)