Source code for smia.utilities.general_utils

import argparse
import calendar
import logging
import os
import time
from datetime import datetime

from spade.message import Message
from spade.template import Template

from smia.logic.exceptions import CriticalError
from smia.utilities import properties_file_utils
from smia.utilities.smia_general_info import SMIAGeneralInfo

[docs] class GeneralUtils: """ This class contains some general utils to ben used by any module. """
[docs] @staticmethod def configure_logging(): """ This method configures the logging to be used by all modules. It specifies different colors to improve the readability of the console and adds new levels to the printouts related to ACL and interaction messages. """ asset_level_num = 35 fipa_acl_level_num = 36 if ((logging.getLevelName("ASSETINFO")) == asset_level_num or (logging.getLevelName("ACLINFO") == fipa_acl_level_num)): # In this case the logging is already configured return logging.addLevelName(asset_level_num, "ASSETINFO") logging.addLevelName(fipa_acl_level_num, "ACLINFO") def assetinfo(self, message, *args, **kwargs): if self.isEnabledFor(asset_level_num): self._log(asset_level_num, message, args, **kwargs) logging.Logger.assetinfo = assetinfo def aclinfo(self, message, *args, **kwargs): if self.isEnabledFor(fipa_acl_level_num): self._log(fipa_acl_level_num, message, args, **kwargs) logging.Logger.aclinfo = aclinfo handler = logging.StreamHandler() formatter = GeneralUtils.ColoredFormatter('%(levelname)s: %(message)s') handler.setFormatter(formatter) logging.getLogger('').addHandler(handler) logging.getLogger('').setLevel(logging.INFO) # Set the default logging level # Create a file handler file_handler = logging.FileHandler(SMIAGeneralInfo.LOG_FOLDER_PATH + '/' + SMIAGeneralInfo.SMIA_LOG_FILENAME) file_handler.setFormatter(logging.Formatter(GeneralUtils.ColoredFormatter.FORMAT_COMPLEX)) logging.getLogger('').addHandler(file_handler)
[docs] class ColoredFormatter(logging.Formatter): """ This class contains the format of all the levels of the logging, including the color of each of them. """ FORMAT_SIMPLE = "%(asctime)s [%(name)s] [%(levelname)s] %(message)s" FORMAT_COMPLEX = "%(asctime)s [%(name)s] [%(levelname)s] %(message)s line:%(lineno)d" RESET = '\x1b[0m' COLORS = { logging.DEBUG: '\x1b[94m' + FORMAT_SIMPLE + RESET, # Blue logging.INFO: '\x1b[39;20m' + FORMAT_SIMPLE + RESET, # White logging.WARNING: '\x1b[93m' + FORMAT_COMPLEX + RESET, # Yellow logging.ERROR: '\x1b[91m' + FORMAT_COMPLEX + RESET, # Red logging.CRITICAL: '\x1b[41m' + FORMAT_COMPLEX + RESET, # White on Red 35: '\x1b[38;2;255;150;20m' + FORMAT_SIMPLE + RESET, # Purple (for the interaction level) 36: '\x1b[38;2;0;255;255m' + FORMAT_SIMPLE + RESET # Cyan (for the FIP-ACL level) }
[docs] def format(self, record): log_fmt = self.COLORS.get(record.levelno) formatter = logging.Formatter(log_fmt) return formatter.format(record)
[docs] @staticmethod def print_smia_banner(): """ This method prints the SMIA banner as a string. The banner has been created with Python 'art' library. """ # The banner for the SMIA is set as string, avoiding installing 'art' library (which has been used to create it) banner_str = ("----------------------------------------------\n" + " ______ ____ ____ _____ _\n" + ".' ____ \ |_ \ / _| |_ _| / \ \n" + "| (___ \_| | \/ | | | / _ \ \n" + " _.____`. | |\ /| | | | / ___ \ \n" + "| \____) | _| |_\/_| |_ _| |_ _/ / \ \_ \n" + " \______.' |_____||_____| |_____| |____| |____| \n" + # " \n" + "----------------------------------------------\n" + " v0.2.3 \n" + "----------------------------------------------\n") print(banner_str)
[docs] @staticmethod def update_aas_model(new_aas_model_file): """ This method updates the AAS model file defined for the SMIA. It updates the global variable for the AAS model file name and the value within the properties file of the SMIA Archive. Args: new_aas_model_file (str): name of the file of the new AAS model to update SMIA. """ # The global variable is updated SMIAGeneralInfo.CM_AAS_MODEL_FILENAME = new_aas_model_file # The properties file is updated file_name, file_ext = os.path.splitext(new_aas_model_file) properties_file_utils.set_aas_general_property('model.file', new_aas_model_file) properties_file_utils.set_aas_general_property('model.serialization', file_ext)
[docs] @staticmethod def create_acl_template(performative, ontology): """ This method creates a template aligned with FIPA-ACL standard. Args: performative(str): The performative of the template. ontology(str): The ontology of the template. Returns: spade.template.Template: a SPADE template object. """ custom_template = Template() custom_template.set_metadata('performative', performative) custom_template.set_metadata('ontology', ontology) return custom_template
[docs] @staticmethod def create_acl_msg(receiver, thread, performative, ontology, body): """ This method creates an FIPA-ACL message. Args: receiver (str): The SPADE agent receiver of the ACL message. thread (str): The thread of the ACL message.: performative (str): The performative of the ACL message. ontology (str): The ontology of the ACL message. body: The body of the ACL message. Returns: spade.message.Message: SPADE message object FIPA-ACL-compliant. """ msg = Message(to=receiver, thread=thread) msg.set_metadata('performative', performative) msg.set_metadata('ontology', ontology) msg.body = body # TODO Pensar si iria tambien en metadatos o todo en el body return msg
[docs] @staticmethod def get_sender_from_acl_msg(acl_msg): """ This method returns the identifier of an agent from an ACL message, considering the suffixes that can be added by the XMPP server. Args: acl_msg (spade.message.Message): ACL message object. Returns: str: identifier of the sender of the message. """ if '/' in str(acl_msg.sender): # XMPP server can add a random string to differentiate the agent JID return str(acl_msg.sender).split('/')[0] else: return str(acl_msg.sender)
[docs] @staticmethod def get_current_timestamp(): """ This method returns the current timestamp of the SMIA. Returns: int: current timestamp in milliseconds """ return calendar.timegm(time.gmtime())
[docs] @staticmethod def get_current_date_time(): """ This method returns the current DateTime of the SMIA. The DateTime is obtained from the UNIX timestamp. Returns: str: current DateTime. """ return datetime.fromtimestamp(GeneralUtils.get_current_timestamp())
[docs] class CLIUtils: """This class contains utility methods related to the Command Line Interface."""
[docs] @staticmethod def get_information_from_cli(cli_args): """ This method gets all the information from the Command Line Interface (CLI). This information includes the initial configuration properties or the AAS model or. None of them is mandatory, but they must be consistent (i.e., if only the AAS model is offered, it must be an AASX which include the configuration file). Args: cli_args (list): arguments of the command line interface Returns: str, str, str: init_config, aas_model and ontology values (None if not specified). """ parser = argparse.ArgumentParser(description='Parser for SMIA CLI arguments') parser.add_argument("-m", "--model") # parser.add_argument("-o", "--ontology") parser.add_argument("-c", "--config") args = parser.parse_args(cli_args) return args.config, args.model
[docs] @staticmethod def check_and_save_cli_information(init_config, aas_model): """ This method checks the information added in the CLI and, depending on the result of the check, it gets the necessary information and saves it in the appropriate variable. Args: init_config (str): path to the initialization configuration properties file. aas_model (str): path to the AAS model file. """ # The libraries are imported locally to avoid circular import error from smia.aas_model.aas_model_utils import AASModelUtils from smia.utilities import smia_archive_utils from smia.utilities import properties_file_utils import ntpath if (init_config is None) and (aas_model is None): # If all are none, the CLI information is invalid raise CriticalError("The CLI information is invalid: no argument has been specified. Please specify at " "least one") elif init_config is not None: # If the configuration properties file is defined, its variable is updated init_config_file_name = ntpath.split(init_config)[1] or ntpath.basename(ntpath.split(init_config)[0]) SMIAGeneralInfo.CM_GENERAL_PROPERTIES_FILENAME = init_config_file_name if aas_model is None: # If the configuration file is specified, but not the AAS model, the AAS must be defined in the file # and obtained from there. aas_model_folder_path = properties_file_utils.get_aas_general_property('model.folder') aas_model_file_name = properties_file_utils.get_aas_general_property('model.file') if (aas_model_file_name is None) or (aas_model_folder_path is None): raise CriticalError("The CLI information is invalid: the AAS model has not been specified either " "in the CLI or in the configuration properties file.") smia_archive_utils.copy_file_into_archive(aas_model_folder_path + '/' + aas_model_file_name, SMIAGeneralInfo.CONFIGURATION_FOLDER_PATH) GeneralUtils.update_aas_model(aas_model_file_name) # SMIAGeneralInfo.CM_AAS_MODEL_FILENAME = aas_model_file_name elif aas_model is not None: # If the AAS model file is defined, its variable is updated aas_model_file_name = ntpath.split(aas_model)[1] or ntpath.basename(ntpath.split(aas_model)[0]) GeneralUtils.update_aas_model(aas_model_file_name) # SMIAGeneralInfo.CM_AAS_MODEL_FILENAME = aas_model_file_name if init_config is None: # If the configuration properties file is not defined, it is obtained from inside the AASX package if aas_model_file_name.split(".")[1] != 'aasx': # If it is not an AASX package, the configuration file is not specified raise CriticalError("The configuration file is not specified and is not inside the AAS model as it " "is not an AASX package. Please specify the configuration file or add it to " "an AASX package and pass it as an AAS model.") config_file_path = AASModelUtils.get_configuration_file_path_from_standard_submodel() # smia_archive_utils.copy_file_into_archive(config_file_path, # SMIAGeneralInfo.CONFIGURATION_FOLDER_PATH) init_config_file_name = ntpath.split(config_file_path)[1] or ntpath.basename(ntpath.split(config_file_path)[0]) # The file must be obtained from the AASX package config_file_bytes = AASModelUtils.get_file_bytes_from_aasx_by_path(config_file_path) if config_file_bytes is None: CriticalError("The CLI information is invalid: the initialization configuration file has not " "been specified either in the CLI or inside the AAS model.") with open(SMIAGeneralInfo.CONFIGURATION_FOLDER_PATH + '/' + init_config_file_name, "wb") as binary_file: # Write bytes to file binary_file.write(config_file_bytes) SMIAGeneralInfo.CM_GENERAL_PROPERTIES_FILENAME = init_config_file_name
_logger = logging.getLogger(__name__)
[docs] class DockerUtils: """This class contains utility methods related to SMIA execution in Docker containers."""
[docs] @staticmethod def get_aas_model_from_env_var(): """ This method returns the AAS model path, obtained from the required 'AAS_MODEL_NAME' environmental variable. Returns: str: path to the AAS model to be loaded. """ aas_model_name = os.environ.get('AAS_MODEL_NAME') if aas_model_name is None: _logger.error("The environment variable 'AAS_MODEL_NAME' for the AAS model is not set, so SMIA cannot start. " "Please add the information and restart the container.") return _logger.info('Loaded AAS model: {}'.format(aas_model_name)) aas_model_path = SMIAGeneralInfo.CONFIGURATION_AAS_FOLDER_PATH + '/' + aas_model_name return aas_model_path