diff --git a/src/control.py b/src/control.py index 2cf3e61c29c76407437827d5ab2226d4630a8edb..f5c895248f0d965f8b8ed58a15f293a50528420f 100644 --- a/src/control.py +++ b/src/control.py @@ -184,21 +184,23 @@ class EngineExecutor(Timer): Primarily used for automations performed by the scheduler. """ - _lock = None + timer_store: dict = {} logger = logging.getLogger("AuraEngine") - initialized = None - timer_store = {} - parent_timer = None - child_timer = None - direct_exec = None - timer_id = None - timer_type = None + EVENT_ON_READY = "on_ready" + + _lock = None + direct_exec: bool = None + parent_timer: Timer = None + child_timer: Timer = None + timer_id: str = None + timer_type: str = None + func = None param = None diff = None dt = None - def __init__(self, timer_type="BASE", parent_timer=None, due_time=None, func=None, param=None): + def __init__(self, timer_type:str="BASE", parent_timer:Timer=None, due_time=None, func=None, param=None): """ Constructor @@ -209,7 +211,6 @@ class EngineExecutor(Timer): func (function): The function to be called param (object): Parameter passt to the function """ - self.initialized = False self._lock = Lock() from src.engine import Engine now_unix = Engine.engine_time() @@ -224,9 +225,8 @@ class EngineExecutor(Timer): self.timer_type = timer_type self.timer_id = f"{timer_type}:{func.__name__}:{due_time}" - if not due_time: - diff = 0 - else: + diff = 0 + if due_time: diff = due_time - now_unix self.diff = diff @@ -240,32 +240,22 @@ class EngineExecutor(Timer): else: if diff < 0: msg = f"Timer '{self.timer_id}' is due in the past. Executing immediately ..." - self.logger.error(SU.red(msg)) + self.logger.warn(SU.yellow(msg)) self.exec_now() elif diff == 0: - self.logger.info(f"Timer '{self.timer_id}' to be executed immediately") + self.logger.debug(f"Timer '{self.timer_id}' to be executed immediately") self.exec_now() else: + self.logger.debug(f"Timer '{self.timer_id}' to be executed in default manner") self.exec_timed() - self.start() - - - def on_ready(self, func): - """ - Calls the passed function `func` when the timer is ready. - """ - while self.initialized == False: - timer.sleep(0.001) - self.logger.info(SU.orange("Waiting until the EngineExecutor is done with initialization...")) - - if not self.direct_exec: #TODO Evaluate if we should join for direct exec too - self.join() - func() def wait_for_parent(self): """ - Child timers are dependend on their parents. So let's wait until parents are done with their stuff. + @private + + Child timers are dependend on their parents. So let's wait until parents are done with their stuff => finished execution. + Checks the parent state to be finished every 0.2 seconds. """ if self.parent_timer: while self.parent_timer.is_alive(): @@ -275,22 +265,27 @@ class EngineExecutor(Timer): def exec_now(self): """ + @private + Immediate execution within a thread. It's not stored in the timer store. - Assigns the `timer_id` as the thread name. + It also assigns the `timer_id` as the thread name. """ self.direct_exec = True self.wait_for_parent() thread = Thread(name=self.timer_id, target=self.func, args=(self.param,)) + time.sleep(0.2) thread.start() - self.initialized = True def exec_timed(self): """ - Timed execution in a thread. + @private - Assigns the `timer_id` as the thread name. + Timed execution in a thread. This method instroduces a slight delay to ensure + the thread is properly initialized before starting it. + + It also assigns the `timer_id` as the thread name. """ def wrapper_func(param=None): self.wait_for_parent() @@ -298,18 +293,22 @@ class EngineExecutor(Timer): else: self.func() super().__init__(self.diff, wrapper_func, (self.param,)) self._name = self.timer_id - self.initialized = True + time.sleep(0.2) + self.start() def update_store(self): """ + @private + Adds the instance to the store and cancels any previously existing commands. If a timer with the given ID is already existing but also already executed, then it is not added to the store. In such case the method returns `False`. Returns: - (Boolean): True if the timer has been added to the store + (Boolean): True if the timer has been added to the store. False if the + timer is already existing but dead. """ with self._lock: existing_command = None @@ -337,6 +336,8 @@ class EngineExecutor(Timer): def is_alive(self): """ + @private + Returns true if the command is still due to be executed. """ if self.direct_exec == True: @@ -346,7 +347,7 @@ class EngineExecutor(Timer): def __str__(self): """ - String represenation of the timer. + String representation of the timer. """ return f"[{self.timer_id}] exec at {str(self.dt)} (alive: {self.is_alive()})" @@ -372,7 +373,7 @@ class EngineExecutor(Timer): @staticmethod def log_commands(): """ - Prints a list of recent active and inactive timers. + Prints a list of recent active and inactive timers to the logger. """ msg = SU.blue("\n [ ENGINE COMMAND QUEUE ]\n") EngineExecutor.remove_stale_timers() @@ -383,7 +384,7 @@ class EngineExecutor(Timer): else: for timer in timers: if not timer.parent_timer: - line = f" => {str(timer)}\n"# + line = f" => {str(timer)}\n" if timer.is_alive(): line = SU.green(line) msg += line diff --git a/src/scheduling/scheduler.py b/src/scheduling/scheduler.py index 9b791e94a5363e34d5ec8876de769b2d3b1e132e..b701ca7b1eae4a314805574119e13c994f5439c3 100644 --- a/src/scheduling/scheduler.py +++ b/src/scheduling/scheduler.py @@ -45,17 +45,16 @@ class AuraScheduler(threading.Thread): - Executes engine actions in an automated fashion """ - config = None + config:AuraConfig = None logger = None - engine = None - exit_event = None - timeslot_renderer = None - programme = None + engine:Engine = None + exit_event:threading.Event = None + timeslot_renderer:TimeslotRenderer = None + programme:ProgrammeService = None message_timer = [] fallback = None - is_initialized = None - is_initialized = None - + is_initialized:bool = None + is_engine_ready:bool = None def __init__(self, engine, fallback_manager): @@ -407,10 +406,11 @@ class TimeslotCommand(EngineExecutor): """ Command for triggering start and end of timeslot events. """ - engine = None - config = None + engine: Engine = None + config: AuraConfig = None + - def __init__(self, engine, timeslot): + def __init__(self, engine: Engine, timeslot): """ Constructor @@ -421,12 +421,15 @@ class TimeslotCommand(EngineExecutor): self.config = AuraConfig() self.engine = engine + now_unix = SU.timestamp() fade_out_time = float(self.config.get("fade_out_time")) - start_fade_out = timeslot.end_unix - fade_out_time - self.logger.info(f"Fading out timeslot in {start_fade_out} seconds at {timeslot.timeslot_end} | Timeslot: {timeslot}") - # Initialize the "fade in" EngineExecuter and instatiate a connected child EngineExecuter for "fade out" when the parent is ready + start_fade_in = timeslot.start_unix - now_unix + start_fade_out = timeslot.end_unix - now_unix - fade_out_time + self.logger.debug(f"Fading in timeslot in {start_fade_in} seconds at {SU.fmt_time(timeslot.start_unix)} | Timeslot: {timeslot}") + self.logger.debug(f"Fading out timeslot in {start_fade_out} seconds at {SU.fmt_time(timeslot.end_unix - fade_out_time)} | Timeslot: {timeslot}") + # Initialize the "fade in" EngineExecuter and instantiate a connected child EngineExecuter for "fade out" when the parent is ready super().__init__("TIMESLOT", None, timeslot.start_unix, self.do_start_timeslot, timeslot) - self.on_ready(lambda: EngineExecutor("TIMESLOT", self, start_fade_out, self.do_end_timeslot, timeslot)) + EngineExecutor("TIMESLOT", self, timeslot.end_unix - fade_out_time, self.do_end_timeslot, timeslot) def do_start_timeslot(self, timeslot): @@ -456,10 +459,11 @@ class PlayCommand(EngineExecutor): """ Command for triggering timed preloading and playing as a child command. """ - engine = None - config = None + engine: Engine = None + config: AuraConfig = None + - def __init__(self, engine, entries): + def __init__(self, engine: Engine, entries): """ Constructor @@ -470,11 +474,13 @@ class PlayCommand(EngineExecutor): self.config = AuraConfig() self.engine = engine - start_preload = entries[0].start_unix - self.config.get("preload_offset") + preload_offset = self.config.get("preload_offset") + start_preload = entries[0].start_unix - preload_offset start_play = entries[0].start_unix + self.logger.debug(f"Preloading entries at {SU.fmt_time(start_preload)}, {preload_offset} seconds before playing it at {SU.fmt_time(start_play)}") # Initialize the "preload" EngineExecuter and attach a child `PlayCommand` to the "on_ready" event handler - preload_timer = super().__init__("PRELOAD", None, start_preload, self.do_preload, entries) - self.on_ready(lambda: EngineExecutor("PLAY", self, start_play, self.do_play, entries)) + super().__init__("PRELOAD", None, start_preload, self.do_preload, entries) + EngineExecutor("PLAY", self, start_play, self.do_play, entries) def do_preload(self, entries): @@ -504,7 +510,7 @@ class PlayCommand(EngineExecutor): # Let 'em play anyway ... self.logger.critical(SU.red("PLAY: The entry/entries are not yet ready to be played (Entries: %s)" % ResourceUtil.get_entries_string(entries))) while (entries[-1].status != EntryPlayState.READY): - self.logger.info("PLAY: Wait a little until preloading is done ...") + self.logger.info("PLAY: Wait a little bit until preloading is done ...") time.sleep(2) self.engine.player.play(entries[0], TransitionType.FADE)