diff --git a/doc/user/source/referenz/items/standard_attribute/log_change.rst b/doc/user/source/referenz/items/standard_attribute/log_change.rst index c5800d2001..79c6105cdf 100644 --- a/doc/user/source/referenz/items/standard_attribute/log_change.rst +++ b/doc/user/source/referenz/items/standard_attribute/log_change.rst @@ -42,15 +42,20 @@ vor dem Schreiben eines jeden Logging-Eintrags neu evaluiert. Attribut *log_text* =================== -Das Attribut **log_text** ermöglicht es einen eigenen Text für den Logeintrag festzulegen. **log_text** kann dabei -eine Reihe von Variablen und eval-Ausdrücken enthalten. +Das Attribut **log_text** ermöglicht es, einen eigenen Text für den Logeintrag festzulegen. +Wird das Attribut nicht angegeben, wird versucht, das im smarthome.yaml hinterlegte Attribut ``default_logtext`` +heranzuziehen. Dieses kann die unten angegebenen Variablen enthalten. Ist auch dieses +Attribut nicht angegeben, wird folgende Muster herangezogen: +``Item Change: {id} = {value} - caller: {caller}{source}{dest}`` + +Wird **log_text** angegeben, kann dabei eine Reihe von Variablen und eval-Ausdrücken genutzt werden. .. attention:: **Achtung:** log_text darf keine Single-Quotes (``'``) enthalten! - Falls es aufgrund des YAML Syntaxes notwendig kann der gesamte String für log_text in Single-Quotes (') + Falls es aufgrund des YAML Syntaxes notwendig ist, kann der gesamte String für log_text in Single-Quotes (') eingeschlossen werden. **Beispiel:** ``log_text: 'Alter={age}'`` @@ -123,6 +128,7 @@ geschwungene Klammern zu setzen. Es können auch mehrere eval-Ausdrücke in einen Log Text eingebunden und mit Variablen konfiguriert werden. **Beispiel:** ``log_text: Ergebnis={1+value} für item {id}`` + **Beispiel:** ``log_text: Ergebnis={"Eins" if value == 1 else "Zwei" if value == 2 else 1+value} für item {id}`` @@ -130,26 +136,25 @@ Attribut *log_mapping* ====================== Über das **log_mapping** Attribut kann festgelegt werden, auf welche Werte/Strings der Wert eines Items für das -Logging gemappt werden soll. Das Attribut **log_mapping** enthält dazu in einem String die Beschreibung eines -dicts. Wobei der Key den zu übersetzenden/mappenden Wert angibt und der dazu gehörige Value des dicts den String -angibt, der über die Variable ``{mvalue}`` ausgegeben wird. +Logging gemappt werden soll. Das Attribut **log_mapping** enthält dazu eine Liste mit Wertzuweisungen im folgenden Format: +zu übersetzender/mappender Wert: String, der über die Variable ``{mvalue}`` ausgegeben wird **Beispiel:** .. code-block:: yaml - log_mapping: "{ - '1': 'Eins', - '2': 'Zwei', - '3': 'Drei' - }" + log_mapping: + - 1: 'Eins' + - 2: 'Zwei' + - 3: 'Drei' Attribut *log_rules* ==================== Über das **log_rules** Attribut kann festgelegt werden, welche zusätzliche Regeln für das Erzeugen des Log-Eintrages -anzuwenden sind. Das Attribut **log_rules** enthält dazu in einem String die Beschreibung eines dicts. +anzuwenden sind. Das Attribut **log_rules** enthält dazu eine Liste mit den folgenden möglichen Definitionen: +``lowlimit``, ``highlimit``, ``filter``, ``exclude``, ``itemvalue`` **Beispiel:** @@ -157,13 +162,12 @@ anzuwenden sind. Das Attribut **log_rules** enthält dazu in einem String die Be item: type: num - log_rules: "{ - 'lowlimit' : -1.0, - 'highlimit': 10.0, - 'filter': [1, 2, 5], - 'exclude': '.exclude_values', - 'itemvalue': '.text' - }" + log_rules: + - 'lowlimit' : -1.0 + - 'highlimit': 10.0 + - 'filter': [1, 2, 5] + - 'exclude': '.exclude_values' + - 'itemvalue': '.text' exclude_values: type: list @@ -186,7 +190,7 @@ highlimit weitere Werte zulassen würden bzw. exclude einen der Werte ausschlie lowlimit -------- -``lowlimit`` Ein Wert, der angibt, unterhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll. +Ein Wert, der angibt, unterhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll. Werte werden geschrieben, Wenn **lowlimit** <= **value** ist. **low_limit** kann nur auf Items vom Typ **num** angewendet werden. @@ -195,7 +199,7 @@ Werte werden geschrieben, Wenn **lowlimit** <= **value** ist. highlimit --------- -``highlimit`` Ein Wert, der angibt, oberhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll. +Ein Wert, der angibt, oberhalb welchen Wertes des Items **kein** Logeintrag geschrieben werden soll. Werte werden geschrieben, Wenn **value** < **highlimit** ist. **highlimit** kann nur auf Items vom Typ **num** angewendet werden. @@ -204,7 +208,7 @@ Werte werden geschrieben, Wenn **value** < **highlimit** ist. filter ------ -``filter`` Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag geschrieben werden soll. +Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag geschrieben werden soll. Wenn das Item vom Typ **num** ist, muss die Liste auch numerische Werte (int oder float) enthalten (``'filter': [1, 2, 5, 2.1]``). Falls das Item von einem anderen Datentyp ist, muss die Liste Strings @@ -214,7 +218,7 @@ enthalten (``'filter': ['1', '2', '5']``). exclude ------- -``exclude`` Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag nicht geschrieben werden soll. +Eine Werteliste, die angibt, bei welchen Werten des Items ein Logeintrag nicht geschrieben werden soll. Wenn das Item vom Typ **num** ist, muss die Liste auch numerische Werte (int oder float) enthalten (``'exclude': [1, 2, 5, 2.1]``). Falls das Item von einem anderen Datentyp ist, muss die Liste Strings @@ -224,5 +228,5 @@ enthalten (``'exclude': ['1', '2', '5']``). itemvalue --------- -``itemvalue`` Der absolute oder relative Pfad zu einem Item, dessen Wert ausgelesen werden soll. +Der absolute oder relative Pfad zu einem Item, dessen Wert ausgelesen werden soll. Dies kann beispielsweise dazu genutzt werden, die Lognachricht zur Laufzeit anzupassen. diff --git a/etc/smarthome.yaml.default b/etc/smarthome.yaml.default index 807b318d5f..1af28a58f2 100644 --- a/etc/smarthome.yaml.default +++ b/etc/smarthome.yaml.default @@ -15,7 +15,7 @@ elev: 30 # elevation tz: 'Europe/Berlin' # timezone, the example will be fine for most parts of central Europe default_language: de # default_language is used, where multiple languages are supported (de, if not specified) - +#default_logtext: "Item {id} wurde geändert." # control type casting when assiging values to items # latest or compat_1.2 (compat_1.2 is default for shNG v1.3, latest is default for higher versions) @@ -62,4 +62,3 @@ default_language: de # default_language is used, where multiple languages ar #db: # - sqlite:sqlite3 # - mysql:pymysql - diff --git a/lib/constants.py b/lib/constants.py index 11c6c358a3..fa80e71d03 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -56,12 +56,16 @@ KEY_ON_UPDATE = 'on_update' KEY_ON_CHANGE = 'on_change' -KEY_LOG_CHANGE = 'log_change' -KEY_LOG_LEVEL = 'log_level' -KEY_LOG_TEXT = 'log_text' +KEY_LOG_CHANGE = 'log_change' +KEY_LOG_LEVEL = 'log_level' +KEY_LOG_TEXT = 'log_text' KEY_LOG_MAPPING = 'log_mapping' -KEY_LOG_RULES = 'log_rules' - +KEY_LOG_RULES = 'log_rules' +KEY_LOG_RULES_LOWLIMIT = 'lowlimit' +KEY_LOG_RULES_HIGHLIMIT = 'highlimit' +KEY_LOG_RULES_FILTER = 'filter' +KEY_LOG_RULES_EXCLUDE = 'exclude' +KEY_LOG_RULES_ITEMVALUE = 'itemvalue' KEY_HYSTERESIS_INPUT = 'hysteresis_input' KEY_HYSTERESIS_UPPER_THRESHOLD = 'hysteresis_upper_threshold' KEY_HYSTERESIS_LOWER_THRESHOLD = 'hysteresis_lower_threshold' diff --git a/lib/item/item.py b/lib/item/item.py index 29083ececc..7447f46521 100755 --- a/lib/item/item.py +++ b/lib/item/item.py @@ -48,10 +48,11 @@ KEY_EVAL, KEY_EVAL_TRIGGER, KEY_TRIGGER, KEY_CONDITION, KEY_NAME, KEY_DESCRIPTION, KEY_TYPE, KEY_STRUCT, KEY_REMARK, KEY_INSTANCE, KEY_VALUE, KEY_INITVALUE, PLUGIN_PARSE_ITEM, KEY_AUTOTIMER, KEY_ON_UPDATE, KEY_ON_CHANGE, KEY_LOG_CHANGE, KEY_LOG_LEVEL, KEY_LOG_TEXT, - KEY_LOG_MAPPING, KEY_LOG_RULES, KEY_THRESHOLD, KEY_EVAL_TRIGGER_ONLY, - KEY_ATTRIB_COMPAT, ATTRIB_COMPAT_V12, ATTRIB_COMPAT_LATEST, PLUGIN_REMOVE_ITEM, - KEY_HYSTERESIS_INPUT, KEY_HYSTERESIS_UPPER_THRESHOLD, KEY_HYSTERESIS_LOWER_THRESHOLD, - ATTRIBUTE_SEPARATOR) + KEY_LOG_MAPPING, KEY_LOG_RULES, KEY_LOG_RULES_LOWLIMIT, KEY_LOG_RULES_HIGHLIMIT, + KEY_LOG_RULES_FILTER, KEY_LOG_RULES_EXCLUDE, KEY_LOG_RULES_ITEMVALUE, KEY_THRESHOLD, + KEY_EVAL_TRIGGER_ONLY, KEY_ATTRIB_COMPAT, ATTRIB_COMPAT_V12, ATTRIB_COMPAT_LATEST, + PLUGIN_REMOVE_ITEM, KEY_HYSTERESIS_INPUT, KEY_HYSTERESIS_UPPER_THRESHOLD, + KEY_HYSTERESIS_LOWER_THRESHOLD, ATTRIBUTE_SEPARATOR) from lib.utils import Utils @@ -261,7 +262,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): self._eval_unexpanded = '' self._eval_trigger = False self._eval_on_trigger_only = False - self._trigger = False + self._trigger = None self._trigger_unexpanded = [] self._trigger_condition_raw = [] self._trigger_condition = None @@ -294,6 +295,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): self._log_level_name = None self._log_mapping = {} self._log_rules = {} + self._log_rules_cache = {} self._log_text = None self._fading = False self._fadingdetails = {} @@ -381,6 +383,8 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): ############################################################# for attr, value in config.items(): if not isinstance(value, dict): + log_rules_keys = [KEY_LOG_RULES_LOWLIMIT, KEY_LOG_RULES_HIGHLIMIT, KEY_LOG_RULES_EXCLUDE, + KEY_LOG_RULES_FILTER, KEY_LOG_RULES_ITEMVALUE] if attr in [KEY_NAME, KEY_DESCRIPTION, KEY_TYPE, KEY_STRUCT, KEY_VALUE, KEY_INITVALUE, KEY_EVAL_TRIGGER_ONLY]: if attr == KEY_INITVALUE: attr = KEY_VALUE @@ -442,14 +446,32 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): setattr(self, '_log_level_name', 'INFO') setattr(self, '_log_level', logging.getLevelName('INFO')) elif attr in [KEY_LOG_MAPPING]: - if value != '': + if isinstance(value, list): + try: + value_dict = {k: v for od in value for k, v in od.items()} + setattr(self, '_log_mapping', value_dict) + except Exception as e: + logger.warning(f"Item {self._path}: Invalid list data for attribute '{KEY_LOG_MAPPING}': {value} - Exception: {e}") + elif value != '': try: value_dict = ast.literal_eval(value) setattr(self, '_log_mapping', value_dict) except Exception as e: logger.warning(f"Item {self._path}: Invalid data for attribute '{KEY_LOG_MAPPING}': {value} - Exception: {e}") elif attr in [KEY_LOG_RULES]: - if value != '': + if isinstance(value, list): + try: + value_dict = {} + for od in value: + for k, v in od.items(): + if k in log_rules_keys: + value_dict[k] = v + else: + logger.warning(f"Item {self._path}: Ignoring '{k}' as it is not a valid log rule") + setattr(self, '_log_rules', value_dict) + except Exception as e: + logger.warning(f"Item {self._path}: Invalid list data for attribute '{KEY_LOG_RULES}': {value} - Exception: {e}") + elif value != '': try: value_dict = ast.literal_eval(value) setattr(self, '_log_rules', value_dict) @@ -2158,7 +2180,9 @@ def __run_on_change(self, value=None, caller=None, source=None, dest=None): def _log_build_standardtext(self, value, caller, source=None, dest=None): - + if self._sh.get_defaultlogtext() is not None: + self._log_text = self._sh.get_defaultlogtext().replace("'", '"') + return self._log_build_text(value, caller, source, dest) log_src = '' if source is not None: log_src += ' (' + source + ')' @@ -2187,11 +2211,10 @@ def _log_build_text(self, value, caller, source=None, dest=None): pname = self.__parent._name pid = self.__parent._path mvalue = self._log_mapping.get(value, value) - lowlimit = self._get_rule('lowlimit') - highlimit = self._get_rule('highlimit') - filter = self._get_rule('filter') - exclude = self._get_rule('exclude') - + lowlimit = self._log_rules_cache.get('lowlimit') + highlimit = self._log_rules_cache.get('highlimit') + filter = self._log_rules_cache.get('filter') + exclude = self._log_rules_cache.get('exclude') sh = self._sh shtime = self.shtime time = shtime.now().strftime("%H:%M:%S") @@ -2203,24 +2226,23 @@ def _log_build_text(self, value, caller, source=None, dest=None): try: entry = self._log_rules.get('itemvalue', None) if entry is not None: - item = self.get_absolutepath(entry.strip().replace("sh.", ""), "log_rules") + item = self.get_absolutepath(entry.strip().replace("sh.", ""), KEY_LOG_CHANGE) itemvalue = str(_items_instance.return_item(item).property.value) else: itemvalue = None except Exception as e: logger.error(f"{id}: Invalid item in log_text '{self._log_text}'" - f" or log_rules '{self._log_rules}' - (Exception: {e})") + f" or log_rules '{self._log_rules}' - Exception: {e}") itemvalue = "INVALID" - import math import lib.userfunctions as uf env = lib.env - + self._log_text = self._log_text.replace("'", '"') try: #logger.warning(f"self._log_text: {self._log_text}, type={type(self._log_text)}") txt = eval(f"f'{self._log_text}'") except Exception as e: - logger.error(f"{id}: Invalid log_text template '{self._log_text}' - (Exception: {e})") + logger.error(f"{id}: Invalid log_text template '{self._log_text}' - Exception: {e}") txt = self._log_text return txt @@ -2231,21 +2253,24 @@ def convert_entry(entry, to): if isinstance(returnvalue, str) and to != "str": try: # try to get value from item - item = self.get_absolutepath(entry.strip().replace("sh.", ""), "log_rules") + item = self.get_absolutepath(entry.strip().replace("sh.", ""), KEY_LOG_CHANGE) returnvalue = _items_instance.return_item(item).property.value - except Exception as e: - pass + except Exception: + if to == "list": + returnvalue = [entry] if isinstance(returnvalue, (str, int)) and to == "num": try: returnvalue = float(returnvalue) - except ValueError as e: + except ValueError: returnvalue = None elif isinstance(entry, list): entry = [convert_entry(val, self._type) for val in entry] + elif not isinstance(returnvalue, list) and to == "list": + returnvalue = [returnvalue] elif isinstance(returnvalue, (float, int)) and to == "str": returnvalue = str(returnvalue) if returnvalue is None: - logger.warning(f"Given log_rules entry '{entry}' is invalid.") + returnvalue = {'value': None, 'issue': f"Given log_rules entry '{entry}' for {rule_entry} is invalid"} return returnvalue defaults = {'filter': [], 'exclude': [], 'lowlimit': None, 'highlimit': None} @@ -2261,21 +2286,70 @@ def _log_on_change(self, value, caller, source=None, dest=None): Write log, if Item has attribute log_change set :return: """ - if self._log_change_logger is not None: + issue_list = [] + low_limit = self._get_rule('lowlimit') + if isinstance(low_limit, dict): + issue = low_limit.get('issue') + issue_list.append(issue) + low_limit = None + high_limit = self._get_rule('highlimit') + if isinstance(high_limit, dict): + issue = high_limit.get('issue') + issue_list.append(issue) + high_limit = None + if self._type != 'num' and low_limit: + issue = f"Low limit {low_limit} given, however item is not num type - ignoring" + issue_list.append(issue) + low_limit = None + if self._type != 'num' and high_limit: + issue = f"High limit {high_limit} given, however item is not num type - ignoring" + issue_list.append(issue) + high_limit = None + if low_limit is not None and high_limit is not None and low_limit >= high_limit: + issue = f"Low limit {low_limit} >= High limit {high_limit} - ignoring high limit" + issue_list.append(issue) + high_limit = None filter_list = self._get_rule('filter') + if isinstance(filter_list, dict): + issue = filter_list.get('issue') + issue_list.append(issue) + filter_list = [] + f_list = [] + for f in filter_list: + if type(value) != type(f): + issue = f"Filter entry {f} is type {type(f)}, item is {self._type} - ignoring" + issue_list.append(issue) + else: + f_list.append(f) + filter_list = f_list exclude_list = self._get_rule('exclude') + if isinstance(exclude_list, dict): + issue = exclude_list.get('issue') + issue_list.append(issue) + exclude_list = [] + e_list = [] + for e in exclude_list: + if type(value) != type(e): + issue = f"Exclude entry {e} is type {type(e)}, item is {self._type} - ignoring" + issue_list.append(issue) + else: + e_list.append(e) + exclude_list = e_list if filter_list != [] and exclude_list != []: - logger.warning(f"Defining filter AND exclude does not work. " - f"Ignoring exclude list {exclude_list} " - f"Using filter: {filter_list}") + issue = f"Defining filter AND exclude does not work - ignoring exclude list" + issue_list.append(issue) + exclude_list = [] + if issue_list and self._log_rules_cache.get('issues') != issue_list: + logger.warning(f"Item {self._path} log_rules has issues: {', '.join(issue_list)}. " + f"Cleaned log_rules: lowlimit = {low_limit}, highlimit = {high_limit}, filter = {filter_list}, exclude = {exclude_list}") + + self._log_rules_cache = {'issues': issue_list, 'filter': filter_list, 'exclude': exclude_list, 'lowlimit': low_limit, 'highlimit': high_limit} if self._type == 'num': - low_limit = self._get_rule('lowlimit') if low_limit is not None: if low_limit > float(value): return - high_limit = self._get_rule('highlimit') if high_limit is not None: if high_limit <= float(value): return @@ -2292,7 +2366,6 @@ def _log_on_change(self, value, caller, source=None, dest=None): elif exclude_list != []: if value in exclude_list: return - if self._log_text is None: txt = self._log_build_standardtext(value, caller, source, dest) else: @@ -2310,7 +2383,7 @@ def _log_on_change(self, value, caller, source=None, dest=None): log_level = eval(f"f'{val}'") except Exception as e: log_level = self._log_level_attrib - logger.error(f"{id}: Invalid log_level template '{log_level}' - (Exception: {e})") + logger.error(f"Item {self._path}: Invalid log_level template '{log_level}' - (Exception: {e})") level = log_level.upper() level_name = level if Utils.is_int(level): diff --git a/lib/log.py b/lib/log.py index f200dfdb3d..1ce14e06c3 100644 --- a/lib/log.py +++ b/lib/log.py @@ -49,6 +49,7 @@ class Logs(): DBGHIGH_level = 13 DBGMED_level = 12 DBGLOW_level = 11 + DEVELOP_level = 9 _all_handlers_logger_name = '_shng_all_handlers_logger' _all_handlers = {} @@ -94,6 +95,7 @@ def configure_logging(self, config_filename=''): # - DBGHIGH 13 Debug high level # - DBGMED 12 Debug medium level # - DBGLOW 11 Debug low level + # - DEVELOP 9 Developer level self.logging_levels = {} self.logging_levels[50] = 'CRITICAL' @@ -108,6 +110,7 @@ def configure_logging(self, config_filename=''): # self.logging_levels[13] = 'DBGHIGH' # self.logging_levels[12] = 'DBGMED' # self.logging_levels[11] = 'DBGLOW' + # self.logging_levels[9] = 'DEVELOP' # adjust config dict from logging.yaml: # if logger 'lib.smarthome' is not defined or no level is defined for it, @@ -132,12 +135,14 @@ def configure_logging(self, config_filename=''): self.DBGHIGH_level = 13 self.DBGMED_level = 12 self.DBGLOW_level = 11 + self.DEVELOP_level = 9 # add SmartHomeNG specific loglevels self.add_logging_level('NOTICE', self.NOTICE_level) self.add_logging_level('DBGHIGH', self.DBGHIGH_level) self.add_logging_level('DBGMED', self.DBGMED_level) self.add_logging_level('DBGLOW', self.DBGLOW_level) + self.add_logging_level('DEVELOP', self.DEVELOP_level) try: logging.config.dictConfig(config_dict) @@ -556,15 +561,15 @@ def get_files_to_delete(self): def custom_replace(match): # Replace based on different patterns if any(match.group(i) for i in (1, 2, 3)): - return '\d{4}' + return r'\d{4}' elif any(match.group(i) for i in (4, 7, 10, 13)): - return '\d{1,2}' + return r'\d{1,2}' elif any(match.group(i) for i in (6, 9, 12, 15)): - return '\d{2}' + return r'\d{2}' elif any(match.group(i) for i in (5, 8, 11, 14)): - return '\d{1}' + return r'\d{1}' elif match.group(16): - return '\d+' + return r'\d+' else: return '[0-9.]' diff --git a/lib/smarthome.py b/lib/smarthome.py index a9b1577ce2..b681c8ded9 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -120,6 +120,7 @@ class SmartHome(): _default_language = 'de' _fallback_language_order = 'en,de' _threadinfo_export = False + _default_logtext = None # for scheduler _restart_on_num_workers = 30 @@ -307,7 +308,6 @@ def __init__(self, MODE, extern_conf_dir='', config_etc=False): # setup logging logsetup = self.init_logging(self._log_conf_basename, MODE) - ############################################################# # get shng version information # shngversion.get_plugins_version() may only be called after logging is initialized @@ -495,14 +495,24 @@ def get_defaultlanguage(self): """ return self._default_language - def set_defaultlanguage(self, language): """ - Returns the configured default language of SmartHomeNG + Sets the default language of SmartHomeNG """ self._default_language = language lib.translation.set_default_language(language) + def get_defaultlogtext(self): + """ + Returns the configured default logtext for log_change of SmartHomeNG + """ + return self._default_logtext + + def set_defaultlogtext(self, log_text): + """ + Sets the default logtext for log_change of SmartHomeNG + """ + self._default_logtext = log_text def get_basedir(self): """ diff --git a/tests/mock/core.py b/tests/mock/core.py index 47e69d5079..306ca0651a 100644 --- a/tests/mock/core.py +++ b/tests/mock/core.py @@ -59,6 +59,7 @@ class MockSmartHome(): _base_dir = BASE base_dir = _base_dir # for external modules using that var (backend, ...?) _default_language = 'de' + _default_logtext = None shng_status = {'code': 20, 'text': 'Running'} @@ -181,6 +182,12 @@ def get_defaultlanguage(self): def set_defaultlanguage(self, language): self._default_language = language + def set_defaultlogtext(self, log_text): + self._default_logtext = log_text + + def get_defaultlogtext(self): + return self._default_logtext + def get_basedir(self): """ Function to return the base directory of the running SmartHomeNG installation