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