示例#1
0
 def update(self):
     """ Get data from GPIO through nanpy"""
     if self.node.connected:
         self.check_connection()
         try:
             data = self.node.api.analogRead(
                 self.pin) if self.analog else self.node.api.digitalRead(
                     self.pin)
             if self.type != 'potentiometer':
                 self.previous_state = self._state
                 self._state = data
             else:
                 if (data < self.previous_state - self.buffer) or (
                         data > self.previous_state + self.buffer):
                     self.previous_state = self._state
                     self._state = data
             self.handle_state()
         except (SerialManagerError, SocketManagerError, BrokenPipeError,
                 ConnectionResetError, OSError, socket.timeout) as e:
             if self.node.connected:
                 Logger.log_formatted(
                     LOG_LEVEL["warning"],
                     f'{self.node.key} -> Broken Connection', 'Timeout',
                     'notice')
                 self.node.reset_connection()
             self._pin_setup = False
     else:
         self._pin_setup = False
     return None
示例#2
0
    def next_step(self, event_data=None):
        """ Advance to the next sequnce step
            Makes sure any delays and durations are done """

        # Step must be flagged complete to advance
        if self._step_complete:
            if self.active:
                # If skipping steps trigger unperformed actions
                if not self._step_triggered:
                    if self.evaluate_thresholds():
                        self.trigger()
                # Sequence is already active, advance to next step
                if self._current_step < self.total_steps - 1:
                    self.reset_step()
                    self._current_step += 1
                    self.fire({"event": "SequenceStepStarted"})
                    Logger.log_formatted(
                        LOG_LEVEL["info"],
                        f'Sequence: {FONT_CYAN}{self.name}{FONT_RESET}', f'Step {self._current_step+1}/{self.total_steps}'
                    )
                else:
                    # Last step of sequence completed
                    self.active = False
                    self.fire({"event": "SequenceEnded"})
                    Logger.log_formatted(
                        LOG_LEVEL["info"],
                        f'Sequence {FONT_CYAN}{self.name}{FONT_RESET}', 'Completed', 'success'
                    )
                self.reset_duration()
示例#3
0
 def update(self):
     """ Get data from DHT through nanpy"""
     if self.node.connected:
         try:
             self.check_connection()
             if self._dht:
                 _temp_format = self.mudpi.unit_system == IMPERIAL_SYSTEM
                 temperature = self._dht.readTemperature(_temp_format)
                 humidity = self._dht.readHumidity()
                 data = {
                     'temperature': round(temperature, 2),
                     'humidity': round(humidity, 2)
                 }
                 self._state = data
         except (SerialManagerError, SocketManagerError, BrokenPipeError,
                 ConnectionResetError, OSError, socket.timeout) as e:
             if self.node.connected:
                 Logger.log_formatted(
                     LOG_LEVEL["warning"],
                     f'{self.node.key} -> Broken Connection', 'Timeout',
                     'notice')
                 self.node.reset_connection()
             self._dht = None
     else:
         self._dht = False
     return None
示例#4
0
def get_extension_importer(mudpi, extension, install_requirements=False):
    """ Find or create an extension importer, Loads it if not loaded, 
        Checks cache first.
        
        Set install_requirements to True to also have all requirements 
        checked through pip.
    """

    # First we check if the namespace is disabled. Could be due to errors or configs
    disabled_cache = mudpi.cache.setdefault("disabled_namespaces", {})
    if extension in disabled_cache:
        raise MudPiError(f"Extension is {extension} is disabled.")

    if install_requirements:
        extension_importer = _extension_with_requirements_installed(
            mudpi, extension)
        if extension_importer is not None:
            return extension_importer

    importer_cache = mudpi.cache.setdefault("extension_importers", {})

    try:
        extension_importer = importer_cache.get(extension)
        if extension_importer is not None:
            return extension_importer
    except Exception as error:
        extension_importer = None

    if extension_importer is None:
        extension_importer = _get_custom_extensions(mudpi).get(extension)
        if extension_importer is not None:
            Logger.log(
                LOG_LEVEL["warning"],
                f'{FONT_YELLOW}You are using {extension} which is not provided by MudPi.{FONT_RESET}\nIf you experience errors, remove it.'
            )
            return extension_importer

    # Component not found look in internal extensions
    from mudpi import extensions

    extension_importer = ExtensionImporter.create(mudpi, extension, extensions)

    if extension_importer is not None:
        importer_cache[extension] = extension_importer
        Logger.log_formatted(
            LOG_LEVEL["info"],
            f'{extension_importer.namespace.title()} Ready for Import',
            'Ready', 'success')
    else:
        Logger.log_formatted(LOG_LEVEL["debug"],
                             f'Import Preperations for {extension.title()}',
                             'error', 'error')
        Logger.log(
            LOG_LEVEL["debug"],
            f'{FONT_YELLOW}`{extension.title()}` was not found.{FONT_RESET}')
        disabled_cache[extension] = 'Not Found'
        raise ExtensionNotFound(extension)

    return extension_importer
示例#5
0
    def update(self):
        """ Main run loop for sequence to check
            time past and if it should fire actions """
        if self.mudpi.is_prepared:
            try:
                if self.active:
                    if not self._step_complete:
                        if not self._delay_complete:
                            if self.step_delay is not None:
                                if self.duration > self.step_delay:
                                    self._delay_complete = True
                                    self._delay_actual = self.duration
                                    self.reset_duration()
                                else:
                                    # Waiting break early
                                    return
                            else:
                                self._delay_complete = True
                                self.reset_duration()

                        if self._delay_complete:
                            if not self._step_triggered:
                                if self.evaluate_thresholds():
                                    self.trigger()
                                else:
                                    if self.current_step.get('thresholds') is not None:
                                        # Thresholds failed skip step without trigger
                                        self._step_triggered = True
                                        self._step_complete = True

                        if self.step_duration is not None and not self._step_complete:
                            if self.duration > self.step_duration:
                                self._step_complete = True
                                self._duration_actual = self.duration
                                self.reset_duration()
                            else:
                                # Waiting break early
                                return
                        else:
                            # No duration set meaning step only advances
                            # manualy by calling actions and events. RTM
                            pass

                    if self._step_complete:
                        self.fire({"event": "SequenceStepEnded"})
                        # Logger.log(
                        #     LOG_LEVEL["debug"],
                        #     f'Sequence {FONT_CYAN}{self.name}{FONT_RESET} Step {self._current_step+1} Debug\n' \
                        #     f'Delay: {self.step_delay} Actual: {self._delay_actual} Duration: {self.step_duration} Actual: {self._duration_actual}'
                        # )
                        return self.next_step()
                else:
                    # Sequence is not active.
                    self.reset_duration()
            except Exception as e:
                    Logger.log_formatted(LOG_LEVEL["error"],
                               f'Sequence {self.id}', 'Unexpected Error', 'error')
                    Logger.log(LOG_LEVEL["critical"], e)
示例#6
0
 def run(self, func=None):
     """ Create a thread and return it """
     if not self._thread:
         self._thread = threading.Thread(target=self.work, args=(func, ))
         Logger.log_formatted(LOG_LEVEL["debug"], f"Worker {self.key} ",
                              "Starting", "notice")
         self._thread.start()
         Logger.log_formatted(LOG_LEVEL["info"], f"Worker {self.key} ",
                              "Started", "success")
     return self._thread
示例#7
0
 def reset(self, event_data=None):
     """ Reset the entire sequence """
     self._current_step = 0
     self.reset_step()
     self.fire({
         "event": "SequenceReset"
     })
     Logger.log_formatted(
         LOG_LEVEL["info"],
         f'Sequence {FONT_CYAN}{self.name}{FONT_RESET}', 'Reset', 'warning'
     )
示例#8
0
 def initialize_logging(self, config=None):
     """ Enable logging module and attach to MudPi """
     config = config or self.mudpi.config.to_dict()
     Logger.logger = self.mudpi.logger = Logger(config)
     time.sleep(0.05)
     Logger.log_formatted(
         LOG_LEVEL["info"], "Initializing Logger ", "Complete", 'success'
     )
     Logger.log_to_file(LOG_LEVEL["debug"], "Dumping the config file: ")
     for index, config_item in config.items():
         Logger.log_to_file(LOG_LEVEL["debug"], f'{index}: {config_item}')
     Logger.log_to_file(LOG_LEVEL["debug"], "End of config file dump!\n")
示例#9
0
 def connect(self):
     """ Setup connections for all adaptors """
     connection_data = {}
     for key, adaptor in self.adaptors.items():
         Logger.log_formatted(LOG_LEVEL["debug"],
                              f"Preparing Event System for {key} ",
                              'Pending', 'notice')
         connection_data[key] = adaptor.connect()
         Logger.log_formatted(LOG_LEVEL["info"],
                              f"Event System Ready on {key}  ", 'Connected',
                              'success')
     return connection_data
示例#10
0
def _install_extension_requirements(mudpi, extension):
    """ Installs all the extension requirements """
    cache = mudpi.cache.setdefault('extensions_requirements_installed', {})

    if cache.get(extension.namespace) is not None:
        # Already processed and installed
        return cache[extension.namespace]

    # Handle all the dependencies requirements
    if extension.has_dependencies:
        if extension.import_dependencies():
            for dependency in extension.loaded_dependencies:
                try:
                    dependency_extension = get_extension_importer(
                        mudpi, dependency)
                except Exception as error:
                    Logger.log(
                        LOG_LEVEL["error"],
                        f'Error getting extension <{extension}> dependency: {FONT_YELLOW}{dependency}{FONT_RESET}'
                    )
                if not dependency_extension.install_requirements():
                    Logger.log(
                        LOG_LEVEL["error"],
                        f'Error with extension <{extension}> dependency: {FONT_YELLOW}{dependency}{FONT_RESET} requirements.'
                    )

    if not extension.has_requirements:
        cache[extension.namespace] = extension
        return cache[extension.namespace]

    requirement_cache = mudpi.cache.setdefault('requirement_installed', {})
    for requirement in extension.requirements:
        if requirement not in requirement_cache:
            if not utils.is_package_installed(requirement):
                Logger.log_formatted(
                    LOG_LEVEL["info"],
                    f'{FONT_YELLOW}{extension.namespace.title()}{FONT_RESET} requirements',
                    'Installing', 'notice')
                Logger.log(
                    LOG_LEVEL["debug"],
                    f'Installing package {FONT_YELLOW}{requirement}{FONT_RESET}',
                )
                if not utils.install_package(requirement):
                    Logger.log(
                        LOG_LEVEL["error"],
                        f'Error installing <{extension.title()}> requirement: {FONT_YELLOW}{requirement}{FONT_RESET}'
                    )
                    return False
                requirement_cache[requirement] = True
    # extension.requirements_installed = True
    cache[extension.namespace] = extension
    return cache[extension.namespace]
示例#11
0
 def stop(self, event_data=None):
     """ Stop the sequence """
     if self.active:
         self._current_step = 0
         self.active = False
         self.reset_step()
         self.fire({
             "event": "SequenceStopped"
         })
         Logger.log_formatted(
             LOG_LEVEL["info"],
             f'Sequence {FONT_CYAN}{self.name}{FONT_RESET}', 'Stopped', 'error'
         )
示例#12
0
 def start(self, event_data=None):
     """ Start the sequence """
     if not self.active:
         self._current_step = 0
         self.active = True
         self.reset_step()
         self.fire({
             "event": "SequenceStarted"
         })
         Logger.log_formatted(
             LOG_LEVEL["info"],
             f'Sequence {FONT_CYAN}{self.name}{FONT_RESET}', 'Started', 'success'
         )
示例#13
0
 def update(self):
     """ Control LCD display nanpy"""
     if self.node.connected:
         try:
             self.check_connection()
             super().update()
         except (SerialManagerError, SocketManagerError, BrokenPipeError,
                 ConnectionResetError, OSError, socket.timeout) as e:
             if self.node.connected:
                 Logger.log_formatted(
                     LOG_LEVEL["warning"],
                     f'{self.node.key} -> Broken Connection', 'Timeout',
                     'notice')
                 self.node.reset_connection()
             self._lcd = None
     return None
示例#14
0
 def get_or_load_interface(self, interface_name):
     """ Get a interface from the extension, import it if not in cache"""
     cache = self.mudpi.cache.setdefault("interface_modules", {})
     component_fullname = f'{self.namespace}.{interface_name}'
     if component_fullname not in cache:
         Logger.log_formatted(
             LOG_LEVEL["debug"],
             f'Importing {self.namespace}:{interface_name}', 'Pending',
             'notice')
         cache[component_fullname] = \
             importlib.import_module(f'{self.extension_path}.{interface_name}')
         Logger.log_formatted(
             LOG_LEVEL["info"],
             f'Imported {self.namespace}:{interface_name}', 'Success',
             'success')
     return cache[component_fullname]
示例#15
0
 def work(self, func=None):
     while self.mudpi.is_prepared:
         if self.mudpi.is_running:
             if not self._server_running:
                 self._server = threading.Thread(target = self.server, args = ())
                 self._server.start()
                 self._server_running = True
         time.sleep(0.1)
     self._server_ready.clear()
     # Connect a client to prevent hanging on accept()
     with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         s.connect((self.host, self.port))
     self._server.join()
     if len(self.client_threads) > 0:
         for client in self.client_threads:
             client.join()
     Logger.log_formatted(LOG_LEVEL['info'], f'Socket Server {self.key}', 'Offline', 'error')
示例#16
0
 def server(self):
     """ Socket server main loop """
     self.sock.listen(0) # number of clients to listen for.
     Logger.log_formatted(LOG_LEVEL['info'], 'MudPi Server', 'Online', 'success')
     while self._server_ready.is_set():
         try:
             client, address = self.sock.accept()
             client.settimeout(600)
             ip, port = client.getpeername()
             Logger.log(LOG_LEVEL['info'], f'Socket Client {port} from {ip} Connected')
             t = threading.Thread(target = self.listenToClient, args = (client, address, ip))
             self.client_threads.append(t)
             t.start()
         except Exception as e:
             Logger.log(LOG_LEVEL['error'], e)
             time.sleep(1)
     self.sock.close()
示例#17
0
    def __init__(self, mudpi, redis_conf=None):
        self.mudpi = mudpi
        self.states = {}
        self._lock = threading.RLock()
        host = '127.0.0.1'
        port = 6379
        try:
            if redis_conf:
                host = redis_conf.get('host', '127.0.0.1')
                port = redis_conf.get('port', 6379)
            self.redis = redis.Redis(host=host, port=port)
        except Exception as error:
            Logger.log(LOG_LEVEL["error"],
                       f"State Manager Error Connecting to Redis")

        self.restore_states()

        Logger.log_formatted(LOG_LEVEL["info"], f"Preparing State Manager ",
                             "Complete", "success")
示例#18
0
 def update(self):
     """ Wrap the failsafe detection in connection handler for node """
     if self.node.connected:
         self.check_connection()
         try:
             # Pass to the Base Toggle for failsafe handling
             super().update()
         except (SerialManagerError, SocketManagerError, BrokenPipeError,
                 ConnectionResetError, OSError, socket.timeout) as e:
             if self.node.connected:
                 Logger.log_formatted(
                     LOG_LEVEL["warning"],
                     f'{self.node.key} -> Broken Connection', 'Timeout',
                     'notice')
                 self.node.reset_connection()
             self._pin_setup = False
     else:
         self._pin_setup = False
     return None
示例#19
0
 def work(self, func=None):
     """ Perform work each cycle like checking devices,
         polling sensors, or listening to events. 
         Worker should sleep based on `update_interval`
     """
     while self.mudpi.is_prepared:
         if self.mudpi.is_running:
             if callable(func):
                 func()
             for key, component in self.components.items():
                 if component.should_update:
                     component.update()
                     component.store_state()
             self.reset_duration()
             self._wait(self.update_interval)
     # # MudPi Shutting Down, Perform Cleanup Below
     Logger.log_formatted(LOG_LEVEL["debug"], f"Worker {self.key} ",
                          "Stopping", "notice")
     for key, component in self.components.items():
         component.unload()
     Logger.log_formatted(LOG_LEVEL["info"], f"Worker {self.key} ",
                          "Offline", "error")
示例#20
0
    def shutdown(self, data=None):
        """ Signal MudPi to Stop and Unload """
        self.stop()
        self.events.publish('core', {'event': 'ShuttingDown'})
        self.unload_extensions()
        self.thread_events['mudpi_running'].clear()
        self.state = CoreState.not_running

        _closed_threads = []
        # First pass of threads to find out which ones are slower
        for thread_name, thread in self.threads.items():
            thread.join(2)
            if not thread.is_alive():
                _closed_threads.append(thread_name)
            else:
                Logger.log_formatted(
                    LOG_LEVEL["warning"],
                    f"Worker {thread_name} is Still Stopping ", "Delayed",
                    "notice")

        for _thread in _closed_threads:
            del self.threads[_thread]

        # Now attempt to clean close slow threads
        _closed_threads = []
        for thread_name, thread in self.threads.items():
            thread.join(3)
            if not thread.is_alive():
                _closed_threads.append(thread_name)
            else:
                Logger.log_formatted(
                    LOG_LEVEL["error"],
                    f"Worker {thread_name} Failed to Shutdown ",
                    "Not Responding", "error")

        self.events.publish('core', {'event': 'Shutdown'})
        self.events.disconnect()
        return True
示例#21
0
 def listenToClient(self, client, address, ip):
     size = 1024
     while self.mudpi.is_prepared:
         try:
             data = client.recv(size)
             if data:
                 data = decode_event_data(data)
                 if data.get("topic", None) is not None:
                     self.mudpi.events.publish(data["topic"], data)
                     Logger.log(LOG_LEVEL['debug'], f"Socket Event {data['event']} from {data['source']} Dispatched")
                     # response = { "status": "OK", "code": 200 }
                     # client.send(json.dumps(response).encode('utf-8'))
                 else:
                     Logger.log(LOG_LEVEL['error'], f"Socket Data Recieved. {FONT_RED}Dispatch Failed:{FONT_RESET} Missing Data 'Topic'")
                     Logger.log(LOG_LEVEL['debug'], data)
             else:
                 pass
                 # raise error('Client Disconnected')
         except Exception as e:
             Logger.log(LOG_LEVEL['info'], f"Socket Client {ip} Disconnected")
             client.close()
             return False
     Logger.log_formatted(LOG_LEVEL['info'], 'Closing Client Connection ', 'Complete', 'success')
示例#22
0
    def get_or_load_extension(self):
        """ Get the extension base module, import if not """
        module_cache = self.mudpi.cache.setdefault('extension_modules', {})
        if self.namespace not in module_cache:
            Logger.log_formatted(LOG_LEVEL["debug"],
                                 f'Importing {self.namespace.title()}',
                                 'Pending', 'notice')
            module_cache[self.namespace] = importlib.import_module(
                self.extension_path)
            Logger.log_formatted(LOG_LEVEL["info"],
                                 f'Imported {self.namespace.title()}',
                                 'Success', 'success')
        self.module = module_cache[self.namespace]

        extension_cache = self.mudpi.cache.setdefault("extensions", {})
        if self.namespace not in extension_cache:
            if not hasattr(self.module, 'Extension'):
                raise ExtensionNotFound(self.namespace)

            extension = extension_cache[
                self.namespace] = self.module.Extension(self.mudpi)

        return extension_cache[self.namespace]
示例#23
0
 def update(self):
     """ Get data from GPIO through nanpy"""
     if self.node.connected:
         self.check_connection()
         try:
             data = None
             if self.analog:
                 data = self.node.api.analogRead(self.pin)
             else:
                 data = self.node.api.digitalRead(self.pin)
             self._state = data
         except (SerialManagerError, SocketManagerError, BrokenPipeError,
                 ConnectionResetError, OSError, socket.timeout) as e:
             if self.node.connected:
                 Logger.log_formatted(
                     LOG_LEVEL["warning"],
                     f'{self.node.key} -> Broken Connection', 'Timeout',
                     'notice')
                 self.node.reset_connection()
             self._pin_setup = True
     else:
         self._pin_setup = False
     return None
示例#24
0
    def work(self, func=None):
        """ Main NFC read cycle """
        while self.mudpi.is_prepared:
            if self.mudpi.is_running:
                try:
                    with nfc.ContactlessFrontend(self.address) as clf:
                        while self.mudpi.is_running:
                            try:
                                tag = clf.connect(rdwr={
                                    'on-connect':
                                    self.handle_read,
                                    'beep-on-connect':
                                    self.beep_enabled,
                                    'on-release':
                                    self.handle_release
                                },
                                                  terminate=self.terminate)
                                time.sleep(self.read_delay)
                            except Exception as error:
                                Logger.log(
                                    LOG_LEVEL["error"],
                                    f"Worker {self.key} NFC Scan Error: {error}"
                                )
                                time.sleep(2)

                except Exception as error:
                    Logger.log_formatted(
                        LOG_LEVEL["error"],
                        f"Worker {self.key} NFC Reader Error: {error}")
                    time.sleep(2)

        # MudPi Shutting Down, Perform Cleanup Below
        Logger.log_formatted(LOG_LEVEL["debug"], f"Worker {self.key} ",
                             "Stopping", "notice")
        Logger.log_formatted(LOG_LEVEL["info"], f"Worker {self.key} ",
                             "Offline", "error")
示例#25
0
def main(args=None):
    """ The main run entry. """
    if args is None:
        args = sys.argv[1:]

    arguments = get_arguments()

    ###################################
    ### Make a default config file ###
    if arguments.make_config:
        config_location = os.path.join(os.getcwd(), arguments.config)
        print(f"Generating a default config file at {config_location}")
        if os.path.exists(config_location) and not arguments.overwrite:
            print(f'{FONT_RED}{"File already exists and `--overwrite` was not set."}{FONT_RESET}')
            return
        Config().save_to_file(config_location)
        return

    ######################################
    ### Convert v0.9 configs to v0.10+ ###
    if arguments.migrate_config:
        config_location = os.path.join(os.getcwd(), arguments.config)
        print(f"Converting config file at {config_location}")
        config = Config(config_location)
        config.load_from_file(config_location)
        new_config_path = os.path.join(os.getcwd(), arguments.migrate_config)
        if os.path.exists(new_config_path) and not arguments.overwrite:
            print(f"{FONT_RED}File already exists at {new_config_path}.")
            print(f"Use `--overwrite` to save over existing file.{FONT_RESET}")
            return
        # convert the old configs
        convert_old_config(config)
        config.save_to_file(new_config_path)
        print(f"{FONT_GREEN}Successfully converted config to: {new_config_path}{FONT_RESET}")
        return

    #######################
    ### Configurations ###
    config_path = os.path.abspath(os.path.join(os.getcwd(), arguments.config))

    print('Loading MudPi Configs \r', end="", flush=True)

    print(chr(27) + "[2J")
    display_greeting()

    manager = CoreManager()
    manager.load_mudpi_from_config(config_path)

    print(f'{"Loading MudPi Configs ":.<{FONT_PADDING+1}} {FONT_GREEN}Complete{FONT_RESET}')

    #####################
    ### Bootstrapping ###
    
    if arguments.debug:
        print(f'{YELLOW_BACK}DEBUG MODE ENABLED{FONT_RESET}')
        manager.mudpi.config.config["mudpi"]["debug"] = True
        # print(arguments) #DEBUG
        print(f'Config path: {config_path}') #DEBUG
        print(f'Current Directory: {os.getcwd()}')
        # print(f'Config keys {mudpi.config.keys()}')
        time.sleep(1)

    # Logging Module
    try:
        print('Initializing Logger \r', end='', flush=True)
        manager.initialize_logging()
    except Exception as e:
        print(f'{"Initializing Logger  ":.<{FONT_PADDING}} {FONT_RED}Disabled{FONT_RESET}')

    # MudPi Core Systems
    manager.load_mudpi_core()

    Logger.log_formatted(LOG_LEVEL["warning"], "Initializing Core ", "Complete", 'success')

    Logger.log(LOG_LEVEL["debug"], f'{" Detecting Configurations ":_^{FONT_PADDING+8}}')
    # Load the Extension System
    loaded_extensions = manager.load_all_extensions()

    Logger.log_formatted(
        LOG_LEVEL["warning"], f"Loaded {len(loaded_extensions)} Extensions ", "Complete", 'success'
    )

    Logger.log_formatted(LOG_LEVEL["warning"], "MudPi Fully Loaded", 'Complete', 'success')

    # Restore Previous States
    Logger.log_formatted(LOG_LEVEL["info"], "Checking for Previous Component States", 'Pending', 'warning')
    manager.mudpi.restore_states()
    Logger.log_formatted(LOG_LEVEL["warning"], "Restored Previous Component States", 'Complete', 'success')


    #########################
    ### Start All Systems ###
    Logger.log(LOG_LEVEL["debug"], f'{" Start Systems ":_^{FONT_PADDING+8}}')
    Logger.log_formatted(LOG_LEVEL["debug"], "Starting All Workers ", 'Pending', 'notice')
    manager.mudpi.start_workers()
    Logger.log_formatted(LOG_LEVEL["info"], "Started All Workers ", 'Complete', 'success')

    # Everything should be loaded and running
    Logger.log_formatted(LOG_LEVEL["info"], "MudPi Systems  ", 'Online', 'success')
    print(f'{"":_<{FONT_PADDING+8}}\n')

    """ Debug Mode Dump After System Online """
    if arguments.debug and arguments.dump:
        manager.debug_dump(cache_dump=arguments.cache_dump)
        time.sleep(1)


    ###############################
    """ MAIN PROGRAM HEARTBEAT """
    manager.mudpi.start()
    PROGRAM_RUNNING = True
    while PROGRAM_RUNNING:
        try:
            # Keep messages being processed
            manager.mudpi.events.get_message()
            current_clock = datetime.datetime.now().replace(microsecond=0)
            manager.mudpi.events.publish('clock', {"clock":current_clock.strftime("%m-%d-%Y %H-%M-%S"), 
                "date":str(current_clock.date()), "time": str(current_clock.time())})
            for i in range(10):
                time.sleep(0.1)
                manager.mudpi.events.get_message()
        except KeyboardInterrupt as error:
            PROGRAM_RUNNING = False
        except Exception as error:
            Logger.log(
                LOG_LEVEL["error"],
                f"Runtime Error:  {error}"
            )
            PROGRAM_RUNNING = False

    """ PROGRAM SHUTDOWN """
    print(f'{"":_<{FONT_PADDING+8}}')
    Logger.log_formatted(
        LOG_LEVEL["info"],
        "Stopping All Workers for Shutdown  ", 'Pending', 'notice'
    )
    manager.shutdown()

    Logger.log_formatted(
        LOG_LEVEL["info"],
        "All MudPi Systems  ", 'Offline', 'error'
    )
示例#26
0
    def validate_config(self, config):
        """ Validate configs for an extension 
            Returns False or the validated configs
        """

        if not self.namespace:
            Logger.log(
                LOG_LEVEL["error"],
                f'{FONT_YELLOW}{self.namespace.title()}{FONT_RESET} is missing a namespace in `extension.json`'
            )
            return False

        namespace = self.namespace

        if self.extension is None:
            Logger.log(
                LOG_LEVEL["error"],
                f'{FONT_YELLOW}{namespace.title()}{FONT_RESET} is not imported. Call `import_extension()` first!'
            )
            return False

        if config is None:
            Logger.log(
                LOG_LEVEL["error"],
                f'{FONT_YELLOW}{namespace.title()}{FONT_RESET} config is None and unable to validate.'
            )
            return False

        # Check for overrided validate() function to determine if custom validation is set
        validated_config = config
        if self.module.Extension.validate != self.module.Extension.__bases__[
                0].validate:
            # Logger.log_formatted(
            #     LOG_LEVEL["debug"],
            #     f'Checking Extension {namespace} Configuration',
            #     "Validating", 'notice'
            # )
            try:
                validated_config = self.extension.validate(config)
                Logger.log_formatted(LOG_LEVEL["debug"],
                                     f'{namespace.title()} Configuration',
                                     "Validated", 'success')
            except (ConfigError, MudPiError) as error:
                Logger.log_formatted(
                    LOG_LEVEL["error"],
                    f'{namespace.title()} Configuration Validation', "Failed",
                    'error')
                Logger.log(LOG_LEVEL["error"],
                           f'{namespace.title()} validation error: {error}')
                return False
            except Exception as error:
                Logger.log(
                    LOG_LEVEL["error"],
                    f'{namespace.title()} Validator encountered unknown error. \n{error}'
                )
                return False

            return validated_config

        # Custom validator not set proceed with default validation
        # Todo: Change this to a regex match to prevent false matches
        # (i.e. action matches action_other)
        conf_keys = [key for key in config.keys() if namespace in key]
        # Logger.log_formatted(
        #         LOG_LEVEL["debug"],
        #         f'Checking Extension {namespace} Configuration',
        #         "Validating", 'notice'
        #     )

        interfaces = []

        disabled_cache = self.mudpi.cache.setdefault("disabled_namespaces", {})

        for conf_key in conf_keys:
            for interface_config in config[conf_key]:

                # Empty configs found
                if not interface_config:
                    continue

                # List wrapper
                if not isinstance(interface_config, list):
                    interface_config = [interface_config]

                for entry in interface_config:
                    try:
                        # Search for interface property
                        interface_name = entry.get("interface")
                    except AttributeError as error:
                        interface_name = None

                # No interface to load which is ok. Not all extensions
                # support interfaces. i.e. actions
                if interface_name is None:
                    interfaces.append(interface_config)
                    continue

                if interface_name in disabled_cache:
                    # raise MudPiError(f"Interface is in disabled namespace: {interface}.")
                    Logger.log_formatted(
                        LOG_LEVEL["warning"],
                        f'Interface {interface_name} is in disabled namespace',
                        "Disabled", 'error')
                    continue
                # Interface found, attempt to load in order to validate config
                try:
                    interface_importer = get_extension_importer(
                        self.mudpi, interface_name, install_requirements=True)
                except Exception as error:
                    continue

                interface = None
                try:
                    interface_module = interface_importer.get_or_load_interface(
                        namespace)
                    if not hasattr(interface_module, 'Interface'):
                        raise MudPiError(
                            f'No `Interface()` class to load for {namespace}:{interface_name}.'
                        )
                    if utils.is_interface(interface_module.Interface):
                        interface = self.interfaces[
                            interface_name] = interface_module.Interface(
                                self.mudpi, namespace, interface_name)
                    else:
                        raise MudPiError(
                            f'Interface {namespace}:{interface} does not extend BaseInterface.'
                        )
                except MudPiError as error:
                    Logger.log(LOG_LEVEL["error"], error)
                    continue
                except Exception as error:
                    Logger.log_formatted(
                        LOG_LEVEL["error"],
                        f'Interface {interface_name}:{namespace} error during validation ',
                        "Errors", 'error')
                    Logger.log(LOG_LEVEL["debug"], error)
                    disabled_cache[interface_name] = error
                    continue

                validated_interface_config = interface_config

                if interface:
                    try:
                        validated_interface_config = interface.validate(
                            interface_config)
                        Logger.log_formatted(
                            LOG_LEVEL["debug"],
                            f'Configuration for {interface_importer.namespace}:{namespace}',
                            "Validated", 'success')
                    except (ConfigError, MudPiError) as error:
                        Logger.log_formatted(
                            LOG_LEVEL["error"],
                            f'{interface_importer.namespace}:{namespace} Configuration Validation',
                            "Failed", 'error')
                        Logger.log(
                            LOG_LEVEL["error"],
                            f'{namespace.title()} validation error: {error}')
                        continue
                    except Exception as error:
                        Logger.log(
                            LOG_LEVEL["error"],
                            f'{interface_importer.namespace.title()} `validate()` encountered unknown error. \n{error}'
                        )
                        continue

                interfaces.append(validated_interface_config)
        Logger.log_formatted(LOG_LEVEL["debug"],
                             f'{namespace.title()} Configuration', "Validated",
                             'success')
        # Copy old config and replace old data with validated
        validated_config = validated_config.copy()
        for key in conf_keys:
            del validated_config[key]
        validated_config[namespace] = interfaces
        config = validated_config
        return validated_config
示例#27
0
    def import_extension(self, config):
        """ Prepare and import the actual extension module """
        disabled_cache = self.mudpi.cache.setdefault("disabled_namespaces", {})

        if self.mudpi.extensions.exists(self.namespace):
            # Extension already loaded
            self.extension = self.mudpi.extensions.get(self.namespace)
            return self.extension

        # Load all Dependencies and install requirements
        if not self.prepare_for_import():
            # The requirements were not able to install
            return False

        # Import the actual package now that requirements and dependencies are done
        try:
            self.extension = self.get_or_load_extension()
            # Call extension post import hook
            self.extension.extension_imported(importer=self)
        except ImportError as error:
            Logger.log(
                LOG_LEVEL["error"],
                f'Error during import of extension: {FONT_YELLOW}{error}{FONT_RESET}'
            )
            return False
        except Exception as error:
            Logger.log(
                LOG_LEVEL["error"],
                f'Extension {self.namespace} error during import: {FONT_YELLOW}{error}{FONT_RESET}'
            )
            return False

        ### Config ###
        # Now we can deal with the config
        validated_config = self.validate_config(config)

        if not validated_config:
            Logger.log(
                LOG_LEVEL["error"],
                f'Extension {FONT_YELLOW}{self.namespace}{FONT_RESET} has invalid or empty configs.'
            )
            return False

        ### Init ###
        # Call the extension init with the validated configs
        if self.extension.__class__.init == self.extension.__class__.__bases__[
                0].init:
            Logger.log(
                LOG_LEVEL["debug"],
                f"Notice: Extension {self.namespace} did not define an `init()` method."
            )

        Logger.log_formatted(LOG_LEVEL["debug"],
                             f"Initializing {self.namespace.title()}",
                             "Pending", 'notice')
        init_result = self.extension.init(validated_config)

        if init_result:
            Logger.log_formatted(LOG_LEVEL["warning"],
                                 f"Initialized {self.namespace.title()}",
                                 "Success", 'success')
            self.extension.setup_complete = True
            # Call extension post init hook
            self.extension.extension_initialized(
                importer=self, validated_config=validated_config)
        else:
            Logger.log_formatted(
                LOG_LEVEL["error"],
                f"{self.namespace.title()} `init()` failed to return True",
                'Failed', 'error')
            return False

        self.mudpi.extensions.register(self.namespace, self.extension)
        self.mudpi.events.publish('core', {
            'event': 'ExtensionRegistered',
            'namespace': self.namespace
        })
        # Call extension post registry hook
        self.extension.extension_registered(importer=self,
                                            validated_config=validated_config)
        return self.extension
示例#28
0
    def load_all_extensions(self, config=None):
        """ Import extensions for MudPi based on loaded Config """
        config = config or self.mudpi.config.config

        if not self.import_config_dir():
            raise ConfigError("Could not import the config_path and load extensions")


        Logger.log_formatted(
            LOG_LEVEL["warning"], "Detecting Configurations", "Pending", 'notice'
        )

        core_configs = ['mudpi', 'logging', 'debug']
        # Get all the non-core extensions to load
        extensions_to_load = [ 
            key 
            for key in config.keys() 
            if key not in core_configs
        ]
        Logger.log_formatted(
            LOG_LEVEL["warning"], f"Detected {len(extensions_to_load)} Non-Core Configurations", "Complete", 'success'
        )
        
        Logger.log_formatted(
            LOG_LEVEL["warning"], f"Preparing {len(extensions_to_load)} Configurations to be Loaded ", "Pending", 'notice'
        )

        extension_count = len(extensions_to_load)
        extension_error_count = 0
        extensions_with_errors = []
        _extensions_needing_load = extensions_to_load
        _importer_cache = {}
        disabled_cache = self.mudpi.cache.setdefault("disabled_namespaces", {})
        # A loop to fetch all extensions and their dependencies 
        while _extensions_needing_load:
            _extension_load_list = _extensions_needing_load.copy()
            _extensions_needing_load = []

            extension_importers = []

            # Load extension importers for detected configs
            for key in _extension_load_list:
                try:
                    extension_importer = importer.get_extension_importer(self.mudpi, key)
                except ExtensionNotFound as error:
                    extension_importer = None
                    extensions_to_load.remove(key)
                    extension_error_count += 1
                    extensions_with_errors.append(key)
                    disabled_cache[key] = 'Not Found'

                if isinstance(extension_importer, importer.ExtensionImporter):
                    extension_importers.append(extension_importer)

            # Loop through importers and prepare extensions for setup
            for extension_importer in extension_importers:
                # Load dependenies if there are any
                if not extension_importer.dependencies_ready:
                    extension_importer.import_dependencies()

                # Check if all the component dependencies imported
                if extension_importer.dependencies_ready:
                    _importer_cache[extension_importer.namespace] = extension_importer

                    for dependency in extension_importer.loaded_dependencies:
                        if dependency in extensions_to_load:
                            continue

                        extensions_to_load.append(dependency)
                        _extensions_needing_load.append(dependency)

        if extension_error_count > 0:
            Logger.log_formatted(
                LOG_LEVEL["warning"], f"Errors Preparing {extension_error_count} Configurations ", "Errors", 'error'
            )
            Logger.log(
                "debug",
                f'Failed to prepare: {FONT_RED}{", ".join(extensions_with_errors)}{FONT_RESET}'
            )

        if len(extensions_to_load):
            Logger.log_formatted(
                LOG_LEVEL["warning"], f"{len(extensions_to_load)} Configurations Ready to Load ", "Complete", 'success'
            )

            Logger.log(LOG_LEVEL["debug"], f'{" Load Extensions ":_^{FONT_PADDING+8}}')
            Logger.log_formatted(
                LOG_LEVEL["warning"], f"Loading {len(extensions_to_load)} Configurations into Extensions ", "Pending", 'notice'
            )
            Logger.log(
                LOG_LEVEL["debug"],
                f'Loading: {FONT_YELLOW}{", ".join(extensions_to_load)}{FONT_RESET}'
            )

        #  Import and setup the extensions
        self.load_extensions(extensions_to_load, config)

        # Cache important data like requirements installed
        self.mudpi.states.cache()

        return self.mudpi.extensions.all()