def run_daily(self, callback, start, **kwargs): name = self.name now = ha.get_now() today = now.date() event = datetime.datetime.combine(today, start) if event < now: event = event + datetime.timedelta(days=1) handle = self.run_every(callback, event, 24 * 60 * 60, **kwargs) return handle
def run_at(self, callback, start, **kwargs): name = self.name now = ha.get_now() if start < now: raise ValueError("{}: run_at() Start time must be " "in the future".format(self.name)) exec_time = start.timestamp() handle = ha.insert_schedule(name, exec_time, callback, False, None, **kwargs) return handle
def run_minutely(self, callback, start, **kwargs): name = self.name now = ha.get_now() if start is None: event = now + datetime.timedelta(minutes=1) else: event = now event = event.replace(second=start.second) if event < now: event = event + datetime.timedelta(minutes=1) handle = self.run_every(callback, event, 60, **kwargs) return handle
def run_once(self, callback, start, **kwargs): name = self.name now = ha.get_now() today = now.date() event = datetime.datetime.combine(today, start) if event < now: one_day = datetime.timedelta(days=1) event = event + one_day exec_time = event.timestamp() handle = ha.insert_schedule(name, exec_time, callback, False, None, **kwargs) return handle
def run_every(self, callback, start, interval, **kwargs): name = self.name now = ha.get_now() if start < now: raise ValueError("start cannot be in the past") ha.log( conf.logger, "DEBUG", "Registering run_every starting {} in {}s intervals for {}".format( start, interval, name ) ) exec_time = start.timestamp() handle = ha.insert_schedule(name, exec_time, callback, True, None, interval=interval, **kwargs) return handle
def update_sun(): #now = datetime.datetime.now(conf.tz) now = conf.tz.localize(ha.get_now()) mod = -1 while True: try: next_rising_dt = conf.location.sunrise( now + datetime.timedelta(days=mod), local=False) if next_rising_dt > now: break except astral.AstralError: pass mod += 1 mod = -1 while True: try: next_setting_dt = (conf.location.sunset( now + datetime.timedelta(days=mod), local=False)) if next_setting_dt > now: break except astral.AstralError: pass mod += 1 old_next_rising_dt = conf.sun.get("next_rising") old_next_setting_dt = conf.sun.get("next_setting") conf.sun["next_rising"] = next_rising_dt conf.sun["next_setting"] = next_setting_dt if old_next_rising_dt != None and old_next_rising_dt != conf.sun[ "next_rising"]: #dump_schedule() process_sun("next_rising") #dump_schedule() if old_next_setting_dt != None and old_next_setting_dt != conf.sun[ "next_setting"]: #dump_schedule() process_sun("next_setting")
def run(): global was_dst global last_state global reading_messages # Take a note of DST was_dst = is_dst() # Setup sun update_sun() # Create Worker Threads for i in range(conf.threads): t = threading.Thread(target=worker) t.daemon = True t.start() # Read apps and get HA State before we start the timer thread get_ha_state() ha.log(conf.logger, "INFO", "Got initial state") # Load apps readApps(True) last_state = ha.get_now() # Create timer thread t = threading.Thread(target=timer_thread) t.daemon = True t.start() # Enter main loop first_time = True while True: try: if first_time == False: # Get initial state get_ha_state() ha.log(conf.logger, "INFO", "Got initial state") # Load apps readApps(True) last_state = ha.get_now() # # Fire HA_STARTED and APPD_STARTED Events # if first_time == True: process_event({"event_type": "appd_started", "data": {}}) first_time = False else: process_event({"event_type": "ha_started", "data": {}}) headers = {'x-ha-access': conf.ha_key} reading_messages = True messages = SSEClient("{}/api/stream".format(conf.ha_url), verify = False, headers = headers, retry = 3000) for msg in messages: process_message(msg) except: reading_messages = False conf.logger.warning("Not connected to Home Assistant, retrying in 5 seconds") if last_state == None: ha.log(conf.logger, "WARNING", '-'*60) ha.log(conf.logger, "WARNING", "Unexpected error:") ha.log(conf.logger, "WARNING", '-'*60) ha.log(conf.logger, "WARNING", traceback.format_exc()) ha.log(conf.logger, "WARNING", '-'*60) time.sleep(5)
def do_every_second(utc): global was_dst global last_state # Lets check if we are connected, if not give up. if not reading_messages: return try: #now = datetime.datetime.now() #now = now.replace(microsecond=0) now = datetime.datetime.fromtimestamp(utc) conf.now = utc # If we have reached endtime bail out if conf.endtime != None and ha.get_now() >= conf.endtime: ha.log(conf.logger, "INFO", "End time reached, exiting") os._exit(0) if conf.realtime: real_now = datetime.datetime.now().timestamp() delta = abs(utc - real_now) if delta > 1: ha.log(conf.logger, "WARNING", "Scheduler clock skew detected - delta = {} - resetting".format(delta)) return real_now # Update sunrise/sunset etc. update_sun() # Check if we have entered or exited DST - if so, reload apps to ensure all time callbacks are recalculated now_dst = is_dst() if now_dst != was_dst: ha.log(conf.logger, "INFO", "Detected change in DST from {} to {} - reloading all modules".format(was_dst, now_dst)) dump_schedule() ha.log(conf.logger, "INFO", "-" * 40) readApps(True) dump_schedule() was_dst = now_dst #dump_schedule() # Check to see if any apps have changed but only if we have valid state if last_state != None: readApps() # Check to see if config has changed check_config() # Call me suspicious, but lets update state form HA periodically in case we miss events for whatever reason # Every 10 minutes seems like a good place to start if last_state != None and now - last_state > datetime.timedelta(minutes = 10): try: get_ha_state() last_state = now except: conf.log.warn("Unexpected error refreshing HA state - retrying in 10 minutes") # Check on Queue size if q.qsize() > 0 and q.qsize() % 10 == 0: conf.logger.warning("Queue size is {}, suspect thread starvation".format(q.qsize())) # Process callbacks #ha.log(conf.logger, "DEBUG", "Scheduler invoked at {}".format(now)) for name in conf.schedule.keys(): for entry in sorted(conf.schedule[name].keys(), key=lambda uuid: conf.schedule[name][uuid]["timestamp"]): #ha.log(conf.logger, "DEBUG", "{} : {}".format(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(conf.schedule[name][entry]["timestamp"])), time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now)))) if conf.schedule[name][entry]["timestamp"] <= utc: exec_schedule(name, entry, conf.schedule[name][entry]) else: break for k, v in list(conf.schedule.items()): if v == {}: del conf.schedule[k] return utc except: ha.log(conf.error, "WARNING", '-'*60) ha.log(conf.error, "WARNING", "Unexpected error during do_every_second()") ha.log(conf.error, "WARNING", '-'*60) ha.log(conf.error, "WARNING", traceback.format_exc()) ha.log(conf.error, "WARNING", '-'*60) if conf.errorfile != "STDERR" and conf.logfile != "STDOUT": # When explicitly logging to stdout and stderr, suppress # log messages abour writing an error (since they show up anyway) ha.log(conf.logger, "WARNING", "Logged an error to {}".format(conf.errorfile))
def run(): global was_dst global last_state global reading_messages ha.log(conf.logger, "DEBUG", "Entering run()") # Take a note of DST was_dst = is_dst() # Setup sun update_sun() ha.log(conf.logger, "DEBUG", "Creating worker threads ...") # Create Worker Threads for i in range(conf.threads): t = threading.Thread(target=worker) t.daemon = True t.start() ha.log(conf.logger, "DEBUG", "Done") # Read apps and get HA State before we start the timer thread ha.log(conf.logger, "DEBUG", "Calling HA for initial state") while last_state == None: try: get_ha_state() last_state = ha.get_now() except: ha.log(conf.logger, "WARNING", '-' * 60) ha.log(conf.logger, "WARNING", "Unexpected error:") ha.log(conf.logger, "WARNING", '-' * 60) ha.log(conf.logger, "WARNING", traceback.format_exc()) ha.log(conf.logger, "WARNING", '-' * 60) ha.log(conf.logger, "WARNING", "Not connected to Home Assistant, retrying in 5 seconds") time.sleep(5) ha.log(conf.logger, "INFO", "Got initial state") # Load apps ha.log(conf.logger, "DEBUG", "Reading Apps") readApps(True) # wait until all threads have finished initializing while True: with conf.threads_busy_lock: if conf.threads_busy == 0: break ha.log( conf.logger, "INFO", "Waiting for App initialization: {} remaining".format( conf.threads_busy)) time.sleep(1) ha.log(conf.logger, "INFO", "App initialization complete") # Create timer thread # First, update "now" for less chance of clock skew error if conf.realtime: conf.now = datetime.datetime.now().timestamp() ha.log(conf.logger, "DEBUG", "Starting timer thread") t = threading.Thread(target=timer_thread) t.daemon = True t.start() # Enter main loop first_time = True reading_messages = True while True: try: if first_time == False: # Get initial state get_ha_state() last_state = ha.get_now() ha.log(conf.logger, "INFO", "Got initial state") # Let the timer thread know we are in business, and give it time to tick at least once reading_messages = True time.sleep(2) # Load apps readApps(True) while True: with conf.threads_busy_lock: if conf.threads_busy == 0: break ha.log( conf.logger, "INFO", "Waiting for App initialization: {} remaining". format(conf.threads_busy)) time.sleep(1) ha.log(conf.logger, "INFO", "App initialization complete") # # Fire HA_STARTED and APPD_STARTED Events # if first_time == True: process_event({"event_type": "appd_started", "data": {}}) first_time = False else: process_event({"event_type": "ha_started", "data": {}}) headers = {'x-ha-access': conf.ha_key} messages = SSEClient("{}/api/stream".format(conf.ha_url), verify=False, headers=headers, retry=3000) for msg in messages: process_message(msg) except: reading_messages = False ha.log(conf.logger, "WARNING", "Not connected to Home Assistant, retrying in 5 seconds") if last_state == None: ha.log(conf.logger, "WARNING", '-' * 60) ha.log(conf.logger, "WARNING", "Unexpected error:") ha.log(conf.logger, "WARNING", '-' * 60) ha.log(conf.logger, "WARNING", traceback.format_exc()) ha.log(conf.logger, "WARNING", '-' * 60) time.sleep(5)
def today_is_constrained(days): day = ha.get_now().weekday() daylist = [ha.day_of_week(day) for day in days.split(",")] if day in daylist: return False return True