From 992506f47380b6829c4517fa8929ece5cb894ea4 Mon Sep 17 00:00:00 2001 From: Morg42 Date: Sun, 24 Jan 2021 17:12:19 +0100 Subject: [PATCH 01/95] change restart behaviour if run with -f --- bin/smarthome.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/smarthome.py b/bin/smarthome.py index 83964e0d66..133ae68879 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -119,7 +119,10 @@ python_bin = sys.executable if ' ' in python_bin: python_bin = '"'+python_bin+'"' - command = python_bin + ' ' + os.path.join(BASE, 'bin', 'smarthome.py') + command = [python_bin, os.path.join(BASE, 'bin', 'smarthome.py')] + # if started with -f then stay in foreground + if args.foreground: + command.append('-f') try: p = subprocess.Popen(command, shell=True) except subprocess.SubprocessError as e: From 71197355364bae5e3eb1b349e0db8f7862dfcbec Mon Sep 17 00:00:00 2001 From: Morg42 Date: Sat, 6 Feb 2021 20:46:01 +0100 Subject: [PATCH 02/95] first steps for restart fix --- bin/smarthome.py | 28 ++++++++++++++++++++-------- lib/smarthome.py | 10 +++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/bin/smarthome.py b/bin/smarthome.py index 133ae68879..087c8c2a14 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -119,10 +119,20 @@ python_bin = sys.executable if ' ' in python_bin: python_bin = '"'+python_bin+'"' +# TODO Morg: keep original command line arguments? + # if we didn't change the working dir (yet), for example... + # command = [python_bin] + sys.argv command = [python_bin, os.path.join(BASE, 'bin', 'smarthome.py')] - # if started with -f then stay in foreground - if args.foreground: - command.append('-f') + + # if started with parameter to stay in foreground, don't fork + if args.foreground or args.interactive or args.debug: + try: + # function call doesn't return; this process is replaced by the new one + os.execv(python_bin, [python_bin] + sys.argv) + except OSError as e: + print(f'Restart command {command} failed with error {e}') + exit(0) + try: p = subprocess.Popen(command, shell=True) except subprocess.SubprocessError as e: @@ -201,7 +211,13 @@ def _reload_logics(): lib.backup.make_backup_directories(BASE) - if args.interactive: +# TODO Morg: need to do this first, so interactive console can resume - hopefully? + if args.restart: + time.sleep(5) + lib.daemon.kill(PIDFILE, 30) +# TODO Morg: add code to adjust MODE setting based on inofficial parameters + pass + elif args.interactive: MODE = 'interactive' import code import rlcompleter # noqa @@ -236,10 +252,6 @@ def _reload_logics(): elif args.stop: lib.daemon.kill(PIDFILE, 30) exit(0) - elif args.restart: - time.sleep(5) - lib.daemon.kill(PIDFILE, 30) - pass elif args.debug: MODE = 'debug' elif args.quiet: diff --git a/lib/smarthome.py b/lib/smarthome.py index b08d4f491a..e65dbc7843 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -214,6 +214,9 @@ def __init__(self, MODE, extern_conf_dir=''): self._logger = logging.getLogger(__name__) self._logger_main = logging.getLogger(__name__ + '.main') + # keep for checking on restart command + self._mode = MODE + self.initialize_vars() self.initialize_dir_vars() self.create_directories() @@ -386,6 +389,7 @@ def __init__(self, MODE, extern_conf_dir=''): ############################################################# # Check Time +# TODO Morg: why hardcoded check? any added value by this? -> remove? while datetime.date.today().isoformat() < '2016-03-16': # XXX update date time.sleep(5) self._logger.info("Waiting for updated time.") @@ -530,7 +534,7 @@ def init_logging(self, conf_basename='', MODE='default'): conf_basename = self._log_conf_basename #fo = open(conf_basename + YAML_FILE, 'r') doc = lib.shyaml.yaml_load(conf_basename + YAML_FILE, True) - if doc == None: + if doc is None: print() print("ERROR: Invalid logging configuration in file 'logging.yaml'") exit(1) @@ -727,6 +731,9 @@ def stop(self, signum=None, frame=None): def restart(self, source=''): """ This method is used to restart the python interpreter and SmartHomeNG + + If SmartHomeNG was started in one of the foreground modes (-f, -i, -d), + this will break the behaviour and daemonize. """ if self.shng_status['code'] == 30: self._logger.warning("Another RESTART is issued, while SmartHomeNG is restarting. Reason: "+source) @@ -739,6 +746,7 @@ def restart(self, source=''): python_bin = sys.executable if ' ' in python_bin: python_bin = '"'+python_bin+'"' +# TODO Morg: add inofficial parameter to transport original MODE setting command = python_bin + ' ' + os.path.join(self._base_dir, 'bin', 'smarthome.py') + ' -r' self._logger.info("Restart command = '{}'".format(command)) try: From 69a05b6a1fdb9cff7229213bc3234ddff9fcb700 Mon Sep 17 00:00:00 2001 From: aschwith Date: Wed, 1 Sep 2021 21:20:22 +0200 Subject: [PATCH 03/95] lib/db: added connected function to inline docu --- lib/db.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/db.py b/lib/db.py index fa5e317d59..5f1598662f 100644 --- a/lib/db.py +++ b/lib/db.py @@ -49,6 +49,8 @@ class Database(): 'lock()' - acquire the database lock (prevent simultaneous reads/writes) 'release()' - release the database lock 'verify()' - check database connection and reconnect if required + 'connected()' - check if database is connected + The SQL statements executed may have placeholders and parameters which are passed to the execution methods listed above. The following DB-API From 8b5964eac08ab279f3c90352288f74bfdef60157 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 26 Dec 2021 12:32:45 +0100 Subject: [PATCH 04/95] lib.network: timeout detection --- lib/network.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/network.py b/lib/network.py index 00458aaed1..3857f63135 100644 --- a/lib/network.py +++ b/lib/network.py @@ -646,6 +646,16 @@ def send(self, message): self.connect() return False + except TimeoutError: + self.logger.warning(f'Detected timeout on {self._host}, disconnecting, send failed.') + self._is_connected = False + if self._disconnected_callback: + self._disconnected_callback(self) + if self._autoreconnect: + self.logger.debug(f'Autoreconnect enabled for {self._host}') + self.connect() + return False + except Exception as e: # log errors we are not prepared to handle and raise exception for further debugging self.logger.warning(f'Unhandleded error on sending to {self._host}, cannot send data {message}. Error: {e}') raise @@ -732,7 +742,12 @@ def __receive_thread_worker(self): events = waitobj.wait(1000) # BMX for fileno, read, write in events: # BMX if read: - msg = self._socket.recv(4096) + timeout = False + try: + msg = self._socket.recv(4096) + except TimeoutError: + msg = None + timeout = True # Check if incoming message is not empty if msg: # TODO: doing this breaks line separation if multiple lines @@ -777,8 +792,12 @@ def __receive_thread_worker(self): else: if self.__running: - # default state, peer closed connection - self.logger.warning(f'Connection closed by peer {self._host}') + if timeout: + # TimeoutError exception caught + self.logger.warning(f'Connection timed out on peer {self._host}, disconnecting.') + else: + # default state, peer closed connection + self.logger.warning(f'Connection closed by peer {self._host}') self._is_connected = False waitobj.unwatch(self._socket) if self._disconnected_callback is not None: @@ -803,10 +822,10 @@ def __receive_thread_worker(self): self._is_receiving = False return else: - self._log_exception(ex, f'lib.network receive thread died with error: {ex}. Go tell...') + self._log_exception(ex, f'lib.network receive thread died with unexpected error: {ex}. Go tell...') self._is_receiving = False - def _log_exception( self, ex, msg): + def _log_exception(self, ex, msg): self.logger.error(msg + ' If stack trace is necessary, enable debug log') if self.logger.isEnabledFor(logging.DEBUG): From aebaae41da89d7da7ef5083f99e938360934470a Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 26 Dec 2021 15:02:34 +0100 Subject: [PATCH 05/95] lib.network: auto(re)connect on send --- lib/network.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/lib/network.py b/lib/network.py index 3857f63135..10f09a9a8f 100644 --- a/lib/network.py +++ b/lib/network.py @@ -47,6 +47,7 @@ import subprocess import threading import time +from contextlib import suppress from . import aioudp @@ -629,6 +630,11 @@ def send(self, message): except Exception: self.logger.warning(f'Error encoding message for client {self.name}') return False + + # automatically (re)connect on send attempt + if not self._is_connected: + self.connect() + try: if self._is_connected: bytes_sent = self._socket.send(message) @@ -636,18 +642,13 @@ def send(self, message): self.logger.warning(f'Error sending message {message} to host {self._host}: message truncated, sent {bytes_sent} of {len(message)} bytes') else: return False - except BrokenPipeError: - self.logger.warning(f'Detected disconnect from {self._host}, send failed.') - self._is_connected = False - if self._disconnected_callback: - self._disconnected_callback(self) - if self._autoreconnect: - self.logger.debug(f'Autoreconnect enabled for {self._host}') - self.connect() - return False - except TimeoutError: - self.logger.warning(f'Detected timeout on {self._host}, disconnecting, send failed.') + except (BrokenPipeError, TimeoutError) as e: + if e.errno == 60: + # timeout + self.logger.warning(f'Detected timeout on {self._host}, disconnecting, send failed.') + else: + self.logger.warning(f'Detected disconnect from {self._host}, send failed.') self._is_connected = False if self._disconnected_callback: self._disconnected_callback(self) @@ -826,7 +827,7 @@ def __receive_thread_worker(self): self._is_receiving = False def _log_exception(self, ex, msg): - self.logger.error(msg + ' If stack trace is necessary, enable debug log') + self.logger.error(msg + ' -- If stack trace is necessary, enable/check debug log') if self.logger.isEnabledFor(logging.DEBUG): @@ -923,10 +924,8 @@ async def __drain_writer(self): """ Ensure drain() is called. """ - try: + with suppress(ConnectionResetError): await self.writer.drain() - except ConnectionResetError: - pass def send(self, message): """ @@ -1241,11 +1240,9 @@ def close(self): self.__loop.call_soon_threadsafe(self.__loop.stop) while self.__loop.is_running(): pass - try: + with suppress(AttributeError): # thread can disappear between first and second condition test if self.__listening_thread and self.__listening_thread.is_alive(): self.__listening_thread.join() - except AttributeError: # thread can disappear between first and second condition test - pass self.__loop.close() @@ -1377,11 +1374,9 @@ def close(self): self.__loop.stop() time.sleep(0.5) - try: + with suppress(AttributeError): # thread can disappear between first and second condition test if self.__listening_thread and self.__listening_thread.is_alive(): self.__listening_thread.join() - except AttributeError: # thread can disappear between first and second condition test - pass self.__loop.close() async def __start_server(self): From 9b7af4872e00e145a3d80369711df3129f065100 Mon Sep 17 00:00:00 2001 From: msinn Date: Tue, 28 Dec 2021 15:08:22 +0100 Subject: [PATCH 06/95] Set repo version to post 1.9.0 release --- bin/shngversion.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/shngversion.py b/bin/shngversion.py index 21dd836b82..c8a13fc4c9 100644 --- a/bin/shngversion.py +++ b/bin/shngversion.py @@ -72,9 +72,12 @@ # Update auf 1.8.2a wg. Kennzeichnung des Stands als "nach dem v1.8.2 Release" # Update auf 1.8.2b wg. Erweiterung des Item Loggings" # Update auf 1.8.2c wg. Wegen Anpassungen an mem-logging / lib.log -# Update auf 1.8.2d Unterstützung für User-Functions +# Update auf 1.8.2d wg. Unterstützung für User-Functions -shNG_version = '1.8.2d' +# Update auf 1.9.0 wg. Release +# Update auf 1.9a wg. Kennzeichnung des Stands als "nach dem v1.9.0 Release" + +shNG_version = '1.9a' shNG_branch = 'develop' # --------------------------------------------------------------------------------- From 320fcedf7ec2274a5192f0760023401c5721d91e Mon Sep 17 00:00:00 2001 From: msinn Date: Tue, 28 Dec 2021 23:15:45 +0100 Subject: [PATCH 07/95] logging.yaml.default with level NOTICE --- etc/logging.yaml.default | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/etc/logging.yaml.default b/etc/logging.yaml.default index 8c62130600..5f5f930589 100644 --- a/etc/logging.yaml.default +++ b/etc/logging.yaml.default @@ -62,7 +62,7 @@ handlers: #class: logging.handlers.TimedRotatingFileHandler (): lib.log.ShngTimedRotatingFileHandler formatter: shng_simple - level: WARNING + level: NOTICE utc: false when: midnight backupCount: 7 @@ -141,24 +141,24 @@ loggers: lib: # Default logger for SmartHomeNG libraries handlers: [shng_details_file] - level: WARNING + level: NOTICE lib.smarthome: # Add all logging handlers that should receive the initial log lines after a startup # (example below) but leave out the logging handlers that are defined in the root-logger # (otherwise log entries will be doubled). # - # 2020-12-29 11:35:34 WARNING lib.smarthome -------------------- Init SmartHomeNG 1.8.0 -------------------- - # 2020-12-29 11:35:34 WARNING lib.smarthome Running in Python interpreter 'v3.8.3 final' in virtual environment - # 2020-12-29 11:35:34 WARNING lib.smarthome - on Linux-4.9.0-6-amd64-x86_64-with-glibc2.17 (pid=24407) - # 2020-12-29 11:35:35 WARNING lib.smarthome - Nutze Feiertage für Land 'DE', Provinz 'HH', 1 benutzerdefinierte(r) Feiertag(e) definiert - # 2020-12-29 11:36:54 WARNING lib.smarthome -------------------- SmartHomeNG initialization finished -------------------- + # 2020-12-29 11:35:34 NOTICE lib.smarthome -------------------- Init SmartHomeNG 1.8.0 -------------------- + # 2020-12-29 11:35:34 NOTICE lib.smarthome Running in Python interpreter 'v3.8.3 final' in virtual environment + # 2020-12-29 11:35:34 NOTICE lib.smarthome - on Linux-4.9.0-6-amd64-x86_64-with-glibc2.17 (pid=24407) + # 2020-12-29 11:35:35 NOTICE lib.smarthome - Nutze Feiertage für Land 'DE', Provinz 'HH', 1 benutzerdefinierte(r) Feiertag(e) definiert + # 2020-12-29 11:36:54 NOTICE lib.smarthome -------------------- SmartHomeNG initialization finished -------------------- # # logging to shng_details_file is already enabled in logger lib: #handlers: [shng_develop_file] # set to WARNING LEVEL to prevent unwanted log entries and add ONLY start and stop log-messages to further logfiles - level: WARNING + level: NOTICE modules: # Default logger for SmartHomeNG modules From 5cd9231f5de3787670da0556a0a36050a490d237 Mon Sep 17 00:00:00 2001 From: msinn Date: Tue, 28 Dec 2021 23:18:38 +0100 Subject: [PATCH 08/95] logging.yaml.default with level NOTICE --- etc/logging.yaml.default | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/logging.yaml.default b/etc/logging.yaml.default index 5f5f930589..59394b5b2c 100644 --- a/etc/logging.yaml.default +++ b/etc/logging.yaml.default @@ -51,10 +51,10 @@ filters: handlers: shng_warnings_file: - # This handler writes only entries with level WARNING and above + # This handler writes only entries with level NOTICE/WARNING and above # to the file. This allows for a quick check if all is running smooth. # - # To keep the warnings file easy readable, the level should always be WARNING! + # To keep the warnings file easy readable, the level should always be NOTICE! # # The TimedRotatingFileHandler seperates the logentries by day and # keeps the entries of the last seven days in seperate files. From f485615e6e89ccf432f8598944bbc6f88918d14f Mon Sep 17 00:00:00 2001 From: aschwith Date: Wed, 29 Dec 2021 17:47:29 +0100 Subject: [PATCH 09/95] module/websocket: Removed untested warning for command == logic. Command works fine. --- modules/websocket/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/websocket/__init__.py b/modules/websocket/__init__.py index 12ca71db74..d9d73c3369 100644 --- a/modules/websocket/__init__.py +++ b/modules/websocket/__init__.py @@ -480,7 +480,6 @@ async def smartVISU_protocol_v4(self, websocket): elif command == 'logic': answer = {} await self.request_logic(data, client_addr) - self.logger.warning("{} Date: Wed, 29 Dec 2021 19:09:20 +0100 Subject: [PATCH 10/95] modules.websocket: Removed "untested" warning from trigger-logic command --- modules/websocket/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/websocket/__init__.py b/modules/websocket/__init__.py index 12ca71db74..d9d73c3369 100644 --- a/modules/websocket/__init__.py +++ b/modules/websocket/__init__.py @@ -480,7 +480,6 @@ async def smartVISU_protocol_v4(self, websocket): elif command == 'logic': answer = {} await self.request_logic(data, client_addr) - self.logger.warning("{} Date: Wed, 29 Dec 2021 23:18:26 +0100 Subject: [PATCH 11/95] lib.network: fix for udp server eventloop too late --- lib/network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/network.py b/lib/network.py index 10f09a9a8f..d9c8ba855b 100644 --- a/lib/network.py +++ b/lib/network.py @@ -1284,8 +1284,10 @@ def __init__(self, port, host='', name=None): self._close_timeout = 2 # private properties - self.__loop = None self.__coroutine = None + self.__loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.__loop) + self.__server = aioudp.aioUDPServer() self.__listening_thread = None self.__running = True @@ -1315,8 +1317,6 @@ def start(self): return False try: self.logger.info(f'Starting up UDP server socket {self.__our_socket}') - self.__loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.__loop) self.__coroutine = self.__start_server() self.__loop.run_until_complete(self.__coroutine) From 94200714a1c8189e47c52b3d5795800a21b1332c Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:24:20 +0100 Subject: [PATCH 12/95] lib.network: pass timeout parameter --- lib/network.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/network.py b/lib/network.py index d9c8ba855b..5163005862 100644 --- a/lib/network.py +++ b/lib/network.py @@ -223,7 +223,6 @@ def clean_uri(uri, mode='show'): """ # find login data pattern = re.compile('http([s]?)://([^:]+:[^@]+@)') - # possible replacement modes replacement = { 'strip': 'http\\g<1>://', @@ -525,7 +524,7 @@ def data_received_callback(Tcp_client_instance, message) :type terminator: int | bytes | str """ - def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, connect_cycle=5, retry_cycle=30, binary=False, terminator=False): + def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, connect_cycle=5, retry_cycle=30, binary=False, terminator=False, timeout=1): self.logger = logging.getLogger(__name__) # public properties @@ -541,7 +540,7 @@ def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, self._connect_retries = connect_retries self._connect_cycle = connect_cycle self._retry_cycle = retry_cycle - self._timeout = 1 + self._timeout = timeout self._hostip = None self._family = socket.AF_INET @@ -561,7 +560,7 @@ def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, self.__receive_threadlock = threading.Lock() self.__running = True - #self.logger.setLevel(logging.DEBUG) # Das sollte hier NICHT gesetzt werden, sondern in etc/logging.yaml im Logger lib.network konfiguriert werden! + # self.logger.setLevel(logging.DEBUG) # Das sollte hier NICHT gesetzt werden, sondern in etc/logging.yaml im Logger lib.network konfiguriert werden! self._host = host self._port = port From e4ad0bd50d3b45772aac02c8b677db494a8838f7 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 2 Jan 2022 15:03:18 +0100 Subject: [PATCH 13/95] logging: Fixed initialization for cases where logging.yaml contains instructions that thow an exception when SmartHomeNG is not fully configured (requirements installed) --- bin/smarthome.py | 2 +- etc/.gitignore | 1 + etc/logging_min.yaml | 139 +++++++++++++++++++++++++++++++++++++++++++ lib/log.py | 8 +-- lib/smarthome.py | 12 +++- 5 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 etc/logging_min.yaml diff --git a/bin/smarthome.py b/bin/smarthome.py index cc9c1a186b..7b5fc594d6 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -122,7 +122,7 @@ python_bin = '"'+python_bin+'"' command = python_bin + ' ' + os.path.join(BASE, 'bin', 'smarthome.py') try: - p = subprocess.Popen(command, shell=True) + p = subprocess.Popen(command + ' -r', shell=True) except subprocess.SubprocessError as e: print("Restart command '{}' failed with error {}".format(command,e)) time.sleep(10) diff --git a/etc/.gitignore b/etc/.gitignore index 2a6f65fc1a..d4c81877ff 100644 --- a/etc/.gitignore +++ b/etc/.gitignore @@ -6,6 +6,7 @@ # except yaml default-files !holidays.yaml.default !logging.yaml.default +!logging_min.yaml !logic.yaml.default !module.yaml.default !plugin.yaml.default diff --git a/etc/logging_min.yaml b/etc/logging_min.yaml new file mode 100644 index 0000000000..7d193f6e1d --- /dev/null +++ b/etc/logging_min.yaml @@ -0,0 +1,139 @@ +%YAML 1.1 +--- +version: 1 +disable_existing_loggers: false + +formatters: + + # The following sections define the output formats to be used in the different logs + # + shng_simple: + format: '%(asctime)s %(levelname)-8s %(name)-19s %(message)s' + datefmt: '%Y-%m-%d %H:%M:%S' + + shng_detail1: + format: '%(asctime)s %(levelname)-8s %(module)-17s %(threadName)-12s %(message)s -- (%(filename)s:%(funcName)s:%(lineno)d)' + datefmt: '%Y-%m-%d %H:%M:%S %Z' + + shng_detail2: + format: '%(asctime)s %(levelname)-8s %(name)-19s %(module)-19s %(funcName)-12s ln:%(lineno)-3d %(message)s ---- %(threadName)-12s ' + datefmt: '%Y-%m-%d %H:%M:%S %Z' + + shng_items: + format: '%(asctime)s %(levelname)-8s %(module)-12s %(message)s' + datefmt: '%Y-%m-%d %H:%M:%S' + + shng_busmonitor: + # This formatter must be enabled when busmonitor logging from the knx plugin should be used. + format: '%(asctime)s;%(message)s;' + datefmt: '%Y-%m-%d;%H:%M:%S' + + +handlers: + + shng_warnings_file: + # This handler writes only entries with level NOTICE/WARNING and above + # to the file. This allows for a quick check if all is running smooth. + # + # To keep the warnings file easy readable, the level should always be NOTICE! + # + # The TimedRotatingFileHandler seperates the logentries by day and + # keeps the entries of the last seven days in seperate files. + # + #class: logging.handlers.TimedRotatingFileHandler + (): lib.log.ShngTimedRotatingFileHandler + formatter: shng_simple + level: NOTICE + utc: false + when: midnight + backupCount: 7 + filename: ./var/log/smarthome-warnings.log + encoding: utf8 + + shng_details_file: + # This handler writes all entries to the details-file. Log entries with level WARNING + # and above are additionally written to the warnings-file (through the root logger). + # + # The TimedRotatingFileHandler seperates the logentries by day and + # keeps the entries of the last seven days in seperate files. + # + (): lib.log.ShngTimedRotatingFileHandler + formatter: shng_simple + level: DEBUG + utc: false + when: midnight + backupCount: 7 + filename: ./var/log/smarthome-details.log + encoding: utf8 + + console: + class: logging.StreamHandler + formatter: shng_simple + stream: ext://sys.stdout + +loggers: + # The following default loggers should not be changed. If additional logging + # is required, a logger for the specific lib, module or plugin shoud be added. + # + functions: + handlers: [shng_details_file] + level: INFO + + lib: + # Default logger for SmartHomeNG libraries + handlers: [shng_details_file] + level: NOTICE + + lib.smarthome: + # Add all logging handlers that should receive the initial log lines after a startup + # (example below) but leave out the logging handlers that are defined in the root-logger + # (otherwise log entries will be doubled). + # + # 2020-12-29 11:35:34 NOTICE lib.smarthome -------------------- Init SmartHomeNG 1.8.0 -------------------- + # 2020-12-29 11:35:34 NOTICE lib.smarthome Running in Python interpreter 'v3.8.3 final' in virtual environment + # 2020-12-29 11:35:34 NOTICE lib.smarthome - on Linux-4.9.0-6-amd64-x86_64-with-glibc2.17 (pid=24407) + # 2020-12-29 11:35:35 NOTICE lib.smarthome - Nutze Feiertage für Land 'DE', Provinz 'HH', 1 benutzerdefinierte(r) Feiertag(e) definiert + # 2020-12-29 11:36:54 NOTICE lib.smarthome -------------------- SmartHomeNG initialization finished -------------------- + # + # logging to shng_details_file is already enabled in logger lib: + #handlers: [shng_develop_file] + + # set to WARNING LEVEL to prevent unwanted log entries and add ONLY start and stop log-messages to further logfiles + level: NOTICE + + modules: + # Default logger for SmartHomeNG modules + handlers: [shng_details_file] + level: WARNING + + plugins: + # Default logger for SmartHomeNG plugins + handlers: [shng_details_file] + level: WARNING + + logics: + # Default logger for SmartHomeNG logics + # to change the loglevel for all logics, the level can be changed. + # Alternatively individual loggers for certain logics can be added. + handlers: [shng_details_file] + level: WARNING + + # ================ + # special loggers + # ---------------- + + __main__: + handlers: [shng_details_file] + level: WARNING + + +root: + # This is the configuration of the root logger. Additionally to be written to other logs, + # ALL entries are handed to this logger. To keep the warnings file easy readable, + # the level should always be WARNING! + # + # Logging of details (level INFO and DEBUG) should be handled by other loggers and written + # to other log files than the warnings file. + # + level: WARNING + handlers: [shng_warnings_file] diff --git a/lib/log.py b/lib/log.py index da4562f15c..32c04b9b41 100644 --- a/lib/log.py +++ b/lib/log.py @@ -53,7 +53,7 @@ def __init__(self, sh): return - def configure_logging(self, config_dict): + def configure_logging(self, config_dict, config_filename='logging.yaml'): if config_dict == None: print() @@ -87,17 +87,17 @@ def configure_logging(self, config_dict): except Exception as e: #self._logger_main.error(f"Invalid logging configuration in file 'logging.yaml' - Exception: {e}") print() - print("ERROR: Invalid logging configuration in file 'logging.yaml'") + print(f"ERROR: Invalid logging configuration in file '{config_filename}'") print(f" Exception: {e}") print() - exit(1) + return False #self.logger.notice(f"Logs.configure_logging: Level NOTICE = {notice_level} / root_handler_level={root_handler_level}") # Initialize MemLog Handler to output root log entries to smartVISU self.initMemLog() - return + return True def add_logging_level(self, description, value): diff --git a/lib/smarthome.py b/lib/smarthome.py index 504bf4e931..8aa8294ee8 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -510,7 +510,17 @@ def init_logging(self, conf_basename='', MODE='default'): conf_basename = self._log_conf_basename conf_dict = lib.shyaml.yaml_load(conf_basename + YAML_FILE, True) - self.logs.configure_logging(conf_dict) + if not self.logs.configure_logging(conf_dict): + conf_basename = self._log_conf_basename + '_min' + print(f" Trying minimal logging configuration from:") + print(f" {conf_basename + YAML_FILE}") + print() + conf_dict = lib.shyaml.yaml_load(conf_basename + YAML_FILE, True) + if not self.logs.configure_logging(conf_dict, 'logging_min.yaml'): + print("ABORTING") + print() + exit(1) + print("Starting with minimal logging configuration") if MODE == 'interactive': # remove default stream handler logging.getLogger().disabled = True From 7dafd441523308deaa7340bf438cddfde1c2be10 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 2 Jan 2022 15:19:28 +0100 Subject: [PATCH 14/95] add port to connect message --- lib/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/network.py b/lib/network.py index 5163005862..a93fd363ef 100644 --- a/lib/network.py +++ b/lib/network.py @@ -595,11 +595,11 @@ def connect(self): :rtype: bool """ if self._hostip is None: # return False if no valid ip to connect to - self.logger.error(f'No valid IP address to connect to {self._host}') + self.logger.error(f'No valid IP address to connect to {self._host}:{self._port}') self._is_connected = False return False if self._is_connected: # return false if already connected - self.logger.error(f'Already connected to {self._host}, ignoring new request') + self.logger.error(f'Already connected to {self._host}:{self._port}, ignoring new request') return False self.__connect_thread = threading.Thread(target=self._connect_thread_worker, name='TCP_Connect') From 865ebc31761537831684187a48cf615e94293d05 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:55:51 +0100 Subject: [PATCH 15/95] lib.network: add more port logging --- lib/network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/network.py b/lib/network.py index a93fd363ef..8f885f61f2 100644 --- a/lib/network.py +++ b/lib/network.py @@ -667,12 +667,12 @@ def _connect_thread_worker(self): Thread worker to handle connection. """ if not self.__connect_threadlock.acquire(blocking=False): - self.logger.warning(f'Connection attempt already in progress for {self._host}, ignoring new request') + self.logger.warning(f'Connection attempt already in progress for {self._host}:{self._port}, ignoring new request') return if self._is_connected: - self.logger.error(f'Already connected to {self._host}, ignoring new request') + self.logger.error(f'Already connected to {self._host}:{self._port}, ignoring new request') return - self.logger.debug(f'Starting connection cycle for {self._host}') + self.logger.debug(f'Starting connection cycle for {self._host}:{self._port}') self._connect_counter = 0 while self.__running and not self._is_connected: # Try a full connect cycle From 3b09708e2c51c9ed910af62c450867eb6037611f Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 2 Jan 2022 23:16:27 +0100 Subject: [PATCH 16/95] docu: add "updated_by()" function to reference page --- doc/user/source/referenz/items/funktionen.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/user/source/referenz/items/funktionen.rst b/doc/user/source/referenz/items/funktionen.rst index c89ecacbea..baca2bcef2 100644 --- a/doc/user/source/referenz/items/funktionen.rst +++ b/doc/user/source/referenz/items/funktionen.rst @@ -71,6 +71,9 @@ Bitte stattdessen die entsprechenden in SmartHomeNG v1.6 eingeführten Propertie | changed_by() | Liefert einen String zurück, der auf das Objekt hinweist, welches das Item | | | zuletzt geändert hat. | +------------------------+------------------------------------------------------------------------------+ +| updated_by() | Liefert einen String zurück, der auf das Objekt hinweist, durch welches | +| | das Item zuletzt ein Update erfahren hat. | ++------------------------+------------------------------------------------------------------------------+ | prev_value() | Liefert den Wert des Items zurück, den es vor der letzten Änderung hatte. | +------------------------+------------------------------------------------------------------------------+ | prev_update() | Liefert ein *datetime* Objekt mit dem Zeitpunkt des vorletzten Updates des | @@ -112,5 +115,3 @@ Die folgende Beispiel Logik nutzt einige der oben beschriebenen Funktionen: # will in- or decrement the living room light to 100 by a stepping of ``1`` and a timedelta of ``2.5`` seconds. sh.living.light.fade(100, 1, 2.5) - - From 9e8e02a470cfb6a05916125d17814faa70acecf5 Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 3 Jan 2022 11:18:03 +0100 Subject: [PATCH 17/95] Logging: If logging.yaml gives an exception, logging.yaml.default is used --- etc/.gitignore | 1 - etc/logging.yaml.default | 2 +- etc/logging_min.yaml | 139 --------------------------------------- lib/smarthome.py | 12 ++-- 4 files changed, 7 insertions(+), 147 deletions(-) delete mode 100644 etc/logging_min.yaml diff --git a/etc/.gitignore b/etc/.gitignore index d4c81877ff..2a6f65fc1a 100644 --- a/etc/.gitignore +++ b/etc/.gitignore @@ -6,7 +6,6 @@ # except yaml default-files !holidays.yaml.default !logging.yaml.default -!logging_min.yaml !logic.yaml.default !module.yaml.default !plugin.yaml.default diff --git a/etc/logging.yaml.default b/etc/logging.yaml.default index 59394b5b2c..67a96244fc 100644 --- a/etc/logging.yaml.default +++ b/etc/logging.yaml.default @@ -163,7 +163,7 @@ loggers: modules: # Default logger for SmartHomeNG modules handlers: [shng_details_file] - level: WARNING + level: NOTICE plugins: # Default logger for SmartHomeNG plugins diff --git a/etc/logging_min.yaml b/etc/logging_min.yaml deleted file mode 100644 index 7d193f6e1d..0000000000 --- a/etc/logging_min.yaml +++ /dev/null @@ -1,139 +0,0 @@ -%YAML 1.1 ---- -version: 1 -disable_existing_loggers: false - -formatters: - - # The following sections define the output formats to be used in the different logs - # - shng_simple: - format: '%(asctime)s %(levelname)-8s %(name)-19s %(message)s' - datefmt: '%Y-%m-%d %H:%M:%S' - - shng_detail1: - format: '%(asctime)s %(levelname)-8s %(module)-17s %(threadName)-12s %(message)s -- (%(filename)s:%(funcName)s:%(lineno)d)' - datefmt: '%Y-%m-%d %H:%M:%S %Z' - - shng_detail2: - format: '%(asctime)s %(levelname)-8s %(name)-19s %(module)-19s %(funcName)-12s ln:%(lineno)-3d %(message)s ---- %(threadName)-12s ' - datefmt: '%Y-%m-%d %H:%M:%S %Z' - - shng_items: - format: '%(asctime)s %(levelname)-8s %(module)-12s %(message)s' - datefmt: '%Y-%m-%d %H:%M:%S' - - shng_busmonitor: - # This formatter must be enabled when busmonitor logging from the knx plugin should be used. - format: '%(asctime)s;%(message)s;' - datefmt: '%Y-%m-%d;%H:%M:%S' - - -handlers: - - shng_warnings_file: - # This handler writes only entries with level NOTICE/WARNING and above - # to the file. This allows for a quick check if all is running smooth. - # - # To keep the warnings file easy readable, the level should always be NOTICE! - # - # The TimedRotatingFileHandler seperates the logentries by day and - # keeps the entries of the last seven days in seperate files. - # - #class: logging.handlers.TimedRotatingFileHandler - (): lib.log.ShngTimedRotatingFileHandler - formatter: shng_simple - level: NOTICE - utc: false - when: midnight - backupCount: 7 - filename: ./var/log/smarthome-warnings.log - encoding: utf8 - - shng_details_file: - # This handler writes all entries to the details-file. Log entries with level WARNING - # and above are additionally written to the warnings-file (through the root logger). - # - # The TimedRotatingFileHandler seperates the logentries by day and - # keeps the entries of the last seven days in seperate files. - # - (): lib.log.ShngTimedRotatingFileHandler - formatter: shng_simple - level: DEBUG - utc: false - when: midnight - backupCount: 7 - filename: ./var/log/smarthome-details.log - encoding: utf8 - - console: - class: logging.StreamHandler - formatter: shng_simple - stream: ext://sys.stdout - -loggers: - # The following default loggers should not be changed. If additional logging - # is required, a logger for the specific lib, module or plugin shoud be added. - # - functions: - handlers: [shng_details_file] - level: INFO - - lib: - # Default logger for SmartHomeNG libraries - handlers: [shng_details_file] - level: NOTICE - - lib.smarthome: - # Add all logging handlers that should receive the initial log lines after a startup - # (example below) but leave out the logging handlers that are defined in the root-logger - # (otherwise log entries will be doubled). - # - # 2020-12-29 11:35:34 NOTICE lib.smarthome -------------------- Init SmartHomeNG 1.8.0 -------------------- - # 2020-12-29 11:35:34 NOTICE lib.smarthome Running in Python interpreter 'v3.8.3 final' in virtual environment - # 2020-12-29 11:35:34 NOTICE lib.smarthome - on Linux-4.9.0-6-amd64-x86_64-with-glibc2.17 (pid=24407) - # 2020-12-29 11:35:35 NOTICE lib.smarthome - Nutze Feiertage für Land 'DE', Provinz 'HH', 1 benutzerdefinierte(r) Feiertag(e) definiert - # 2020-12-29 11:36:54 NOTICE lib.smarthome -------------------- SmartHomeNG initialization finished -------------------- - # - # logging to shng_details_file is already enabled in logger lib: - #handlers: [shng_develop_file] - - # set to WARNING LEVEL to prevent unwanted log entries and add ONLY start and stop log-messages to further logfiles - level: NOTICE - - modules: - # Default logger for SmartHomeNG modules - handlers: [shng_details_file] - level: WARNING - - plugins: - # Default logger for SmartHomeNG plugins - handlers: [shng_details_file] - level: WARNING - - logics: - # Default logger for SmartHomeNG logics - # to change the loglevel for all logics, the level can be changed. - # Alternatively individual loggers for certain logics can be added. - handlers: [shng_details_file] - level: WARNING - - # ================ - # special loggers - # ---------------- - - __main__: - handlers: [shng_details_file] - level: WARNING - - -root: - # This is the configuration of the root logger. Additionally to be written to other logs, - # ALL entries are handed to this logger. To keep the warnings file easy readable, - # the level should always be WARNING! - # - # Logging of details (level INFO and DEBUG) should be handled by other loggers and written - # to other log files than the warnings file. - # - level: WARNING - handlers: [shng_warnings_file] diff --git a/lib/smarthome.py b/lib/smarthome.py index 8aa8294ee8..6dd811422a 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -511,16 +511,16 @@ def init_logging(self, conf_basename='', MODE='default'): conf_dict = lib.shyaml.yaml_load(conf_basename + YAML_FILE, True) if not self.logs.configure_logging(conf_dict): - conf_basename = self._log_conf_basename + '_min' - print(f" Trying minimal logging configuration from:") - print(f" {conf_basename + YAML_FILE}") + #conf_basename = self._log_conf_basename + YAML_FILE + '.default' + print(f" Trying default logging configuration from:") + print(f" {conf_basename + YAML_FILE + '.default'}") print() - conf_dict = lib.shyaml.yaml_load(conf_basename + YAML_FILE, True) - if not self.logs.configure_logging(conf_dict, 'logging_min.yaml'): + conf_dict = lib.shyaml.yaml_load(conf_basename + YAML_FILE + '.default', True) + if not self.logs.configure_logging(conf_dict, 'logging.yaml.default'): print("ABORTING") print() exit(1) - print("Starting with minimal logging configuration") + print("Starting with default logging configuration") if MODE == 'interactive': # remove default stream handler logging.getLogger().disabled = True From 3b7471b6f61630ca029a67cdc22ee2ef8e672a0c Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 3 Jan 2022 11:19:35 +0100 Subject: [PATCH 18/95] Requirements: all.txt and conf_all.txt contain only requirements for plugins, if shng & Python version requirements for are met --- bin/shngversion.py | 24 +++++----- bin/smarthome.py | 2 +- lib/metadata.py | 66 +++++++++++----------------- lib/shpypi.py | 89 +++++++++++++++++++++++++++++++------ lib/utils.py | 107 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 66 deletions(-) diff --git a/bin/shngversion.py b/bin/shngversion.py index c8a13fc4c9..f07a046fe2 100644 --- a/bin/shngversion.py +++ b/bin/shngversion.py @@ -27,7 +27,7 @@ sys.path.append('..') import plugins.__init__ as plugin_vers - +from lib.utils import Version # Update auf 1.3d wg. neuer item features on_update, on_change # Update auf 1.3e wg. neuer logic features for visu_websocket @@ -116,17 +116,17 @@ def _get_git_data(sub='', printout=False): # --------------------------------------------------------------------------------- def get_shng_main_version(): - return shNG_version + return Version.format( shNG_version ) def get_shng_version(): commit, commit_short, branch, describe = _get_git_data() VERSION = get_shng_main_version() if branch == 'master': - VERSION += '.'+branch+' ('+commit_short+')' + VERSION += '-'+branch+' ('+commit_short+')' elif branch == 'manual': - VERSION += '.'+shNG_branch+' ('+branch+')' + VERSION += '-'+shNG_branch+' ('+branch+')' else: - VERSION += '.'+commit_short+'.'+branch + VERSION += '-'+commit_short+'.'+branch return VERSION def get_shng_branch(): @@ -140,7 +140,7 @@ def get_shng_description(): def get_plugins_version(): commit, commit_short, branch, describe = _get_git_data('plugins') - VERSION = get_shng_main_version() + VERSION = Version.format( get_shng_main_version() ) try: PLUGINS_VERSION = plugin_vers.plugin_release() except: @@ -151,16 +151,16 @@ def get_plugins_version(): PLUGINS_SOURCE_BRANCH = '' if branch == 'master': - VERSION = PLUGINS_VERSION - VERSION += '.'+branch+' ('+commit_short+')' + VERSION = Version.format( PLUGINS_VERSION ) + VERSION += '-'+branch+' ('+commit_short+')' elif branch == 'manual': - VERSION = PLUGINS_VERSION + VERSION = Version.format( PLUGINS_VERSION ) if PLUGINS_SOURCE_BRANCH != '': - VERSION += '.'+PLUGINS_SOURCE_BRANCH + VERSION += '-'+PLUGINS_SOURCE_BRANCH VERSION += ' ('+branch+')' else: - VERSION = PLUGINS_VERSION - VERSION += '.'+commit_short+'.'+branch + VERSION = Version.format( PLUGINS_VERSION ) + VERSION += '-'+commit_short+'.'+branch return VERSION diff --git a/bin/smarthome.py b/bin/smarthome.py index 7b5fc594d6..0ef5463df8 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -112,7 +112,7 @@ from lib.shpypi import Shpypi shpypi = Shpypi.get_instance() if shpypi is None: - shpypi = Shpypi(base=BASE) + shpypi = Shpypi(base=BASE, version=VERSION) core_reqs = shpypi.test_core_requirements(logging=False, pip3_command=args.pip3_command) if core_reqs == 0: diff --git a/lib/metadata.py b/lib/metadata.py index 6c0daaf198..399118b2f6 100644 --- a/lib/metadata.py +++ b/lib/metadata.py @@ -25,6 +25,7 @@ import collections from lib.utils import Utils +from lib.utils import Version import lib.shyaml as shyaml from lib.constants import (YAML_FILE, FOO, META_DATA_TYPES, META_DATA_DEFAULTS) @@ -410,37 +411,20 @@ def test_shngcompatibility(self): :return: True if the SmartHomeNG version is in the supported range :rtype: bool """ - l = str(self._sh.version).split('.') - shng_version = l[0]+'.'+l[1] - if len(l) > 2: - shng_version += '.'+l[2] - l = str(self.get_string('sh_minversion')).split('.') - min_shngversion = l[0] - if len(l) > 1: - min_shngversion += '.'+l[1] - if len(l) > 2: - min_shngversion += '.'+l[2] - - l = str(self.get_string('sh_maxversion')).split('.') - max_shngversion = l[0] - if len(l) > 1: - max_shngversion += '.'+l[1] - if len(l) > 2: - max_shngversion += '.'+l[2] - - mod_version = self.get_string('version') + shng_version = Version.format(self._sh.version.split('-')[0]) + min_shngversion = Version.format(str(self.get_string('sh_minversion'))) + max_shngversion = Version.format(str(self.get_string('sh_maxversion'))) + mod_version = Version.format(self.get_string('version')) if min_shngversion != '': - #r = self._compare_versions(min_shngversion, shng_version, '>', (min_shngversion > shng_version)) # if min_shngversion > shng_version: - if self._compare_versions(min_shngversion, shng_version, '>', (min_shngversion > shng_version)): - logger.error("{0} '{1}': The version {3} of SmartHomeNG is too old for this {0}. It requires at least version v{2}. The {0} was not loaded.".format(self._addon_type, self._addon_name, min_shngversion, shng_version)) + if Version.compare(min_shngversion, shng_version, '>'): + logger.error(f"{self._addon_type} '{self._addon_name}' {mod_version}: SmartHomeNG {shng_version} is too old for this {self._addon_type}. It requires at least version {Version.format(min_shngversion)}. The {self._addon_type} was not loaded.") return False if max_shngversion != '': - #self._compare_versions(max_shngversion, shng_version, '<', (max_shngversion < shng_version)) # if max_shngversion < shng_version: - if self._compare_versions(max_shngversion, shng_version, '<', (max_shngversion < shng_version)): - logger.error("{0} '{1}': The version {3} of SmartHomeNG is too new for this {0}. It requires a version up to v{2}. The {0} was not loaded.".format(self._addon_type, self._addon_name, max_shngversion, shng_version)) + if Version.compare(max_shngversion, shng_version, '<'): + logger.error(f"{self._addon_type} '{self._addon_name}' {mod_version}: SmartHomeNG {shng_version} is too new for this {self._addon_type}. It requires a version up to {Version.format(max_shngversion)}. The {self._addon_type} was not loaded.") return False return True @@ -453,29 +437,31 @@ def test_pythoncompatibility(self): :rtype: bool """ l = sys.version_info - py_version = str(l[0])+'.'+str(l[1]) - - l = str(self.get_string('py_minversion')).split('.') - min_pyversion = l[0] - if len(l) > 1: - min_pyversion += '.'+l[1] - l = str(self.get_string('py_maxversion')).split('.') - max_pyversion = l[0] - if len(l) > 1: - max_pyversion += '.'+l[1] - mod_version = self.get_string('version') + py_version = Version.format(str(l[0])+'.'+str(l[1])+'.'+str(l[2])) + min_pyversion = Version.format(str(self.get_string('py_minversion'))) + max = str(self.get_string('py_maxversion')) + if len(max.split('.')) == 2: + # if given max version has only two parts, make it the max for that version: 3.8 -> 3.8.999 + max += '.999' + max_pyversion = Version.format(str(max)) + mod_version = Version.format(self.get_string('version')) + + #if min_pyversion != '' or max_pyversion != '': + # logger.notice(f"{self._addon_type} '{self._addon_name}' {mod_version}: Python Version: {py_version}, min: {min_pyversion}, max: {max_pyversion}") if min_pyversion != '': #self._compare_versions(min_pyversion, py_version, '>', (min_pyversion > py_version)) # if min_pyversion > py_version: - if self._compare_versions(min_pyversion, py_version, '>', (min_pyversion > py_version)): - logger.error("{0} '{1}': The Python version {3} is too old for this {0}. It requires at least version v{2}. The {0} was not loaded.".format(self._addon_type, self._addon_name, min_pyversion, py_version)) + #if self._compare_versions(min_pyversion, py_version, '>', (min_pyversion > py_version)): + if Version.compare(min_pyversion, py_version, '>'): + logger.error(f"{self._addon_type} '{self._addon_name}' {mod_version}: The Python version {py_version} is too old for this {self._addon_type}. It requires at least version {min_pyversion}. The {self._addon_type} was not loaded.") return False if max_pyversion != '': #self._compare_versions(max_pyversion, py_version, '<', (max_pyversion < py_version)) # if max_pyversion < py_version: - if self._compare_versions(max_pyversion, py_version, '<', (max_pyversion < py_version)): - logger.error("{0} '{1}': The Python version {3} is too new for this {0}. It requires a version up to v{2}. The {0} was not loaded.".format(self._addon_type, self._addon_name, max_pyversion, py_version)) + #if self._compare_versions(max_pyversion, py_version, '<', (max_pyversion < py_version)): + if Version.compare(max_pyversion, py_version, '<'): + logger.error(f"{self._addon_type} '{self._addon_name}' {mod_version}: The Python version {py_version} is too new for this {self._addon_type}. It requires a version up to {max_pyversion}. The {self._addon_type} was not loaded.") return False return True diff --git a/lib/shpypi.py b/lib/shpypi.py index c4b6105126..093e1cce95 100644 --- a/lib/shpypi.py +++ b/lib/shpypi.py @@ -46,7 +46,9 @@ except: pass +import lib.shyaml as shyaml from lib.utils import Utils +from lib.utils import Version from lib.constants import (YAML_FILE) @@ -55,7 +57,7 @@ class Shpypi: - def __init__(self, sh=None, base=None): + def __init__(self, sh=None, base=None, version=None): """ :param smarthome: @@ -72,7 +74,7 @@ def __init__(self, sh=None, base=None): self.logger.critical("A second 'shpypi' object has been created. There should only be ONE instance of class 'Shpypi'!!! Called from: {} {} ({})".format(calframe[1][1], calframe[1][2], calframe[1][3])) _shpypi_instance = self - self.req_files = Requirements_files() + self.req_files = Requirements_files(version) self.scheduler_crontab = ['init', '7 3 * *'] @@ -229,7 +231,7 @@ def test_requirements(self, filepath, logging=True, hard_requirement=True): def test_core_requirements(self, logging=True, pip3_command=None): # build an actual requirements file for core+modules - # req_files = Requirements_files() + # req_files = Requirements_files(self.sh) self.req_files.create_requirementsfile('base') self.req_files.create_requirementsfile('all') self.req_files.create_requirementsfile('core') @@ -263,7 +265,7 @@ def test_base_requirements(self, sh=None): if sh is not None: self.sh = sh # build an actual requirements file for core+modules - # req_files = Requirements_files() + # req_files = Requirements_files(self.sh) self.req_files.create_requirementsfile('base') # test if the requirements of the base.txt file are met @@ -281,7 +283,7 @@ def test_base_requirements(self, sh=None): def test_conf_plugins_requirements(self, plugin_conf_basename, plugins_dir): # import lib.shyaml here, so test_base_requirements() can be run even if ruamel.yaml package is not installed - import lib.shyaml as shyaml +# import lib.shyaml as shyaml if not os.path.isfile(plugin_conf_basename+ YAML_FILE): self.logger.warning("Requirments for configured plugins were not checked because the plugin configuration is not in YAML format") @@ -312,9 +314,13 @@ def test_conf_plugins_requirements(self, plugin_conf_basename, plugins_dir): self._conf_plugin_filelist = [] for plugin in req_dict: - self._conf_plugin_filelist.append(req_dict[plugin]) + use_it, msg = self.req_files._test_plugin_versions(req_dict[plugin]) + if use_it: + self._conf_plugin_filelist.append(req_dict[plugin]) + else: + self.logger.info(f"Not appended: ({msg}) conf_all: {req_dict[plugin]}") - #req_files = Requirements_files() + #req_files = Requirements_files(self.sh) self.req_files.set_conf_plugin_files(self._conf_plugin_filelist) self.req_files.create_requirementsfile('conf_all') @@ -402,6 +408,7 @@ def install_requirements(self, req_type, logging=True, pip3_command=None): if logging: self.logger.warning("Running in a virtualenv environment - installing " + req_type_display + " requirements only to actual virtualenv, please wait...") else: + print() print("Running in a virtualenv environment,") print("installing "+req_type_display+" requirements only to actual virtualenv, please wait...") stdout, stderr = Utils.execute_subprocess('pip3 install -r requirements/'+req_type+'.txt') @@ -1060,11 +1067,16 @@ class Requirements_files(): _plugin_files = [] _core_files = [] # to be a list in the future - def __init__(self): + def __init__(self, version=None): self.logger = logging.getLogger(__name__) self._conf_plugin_files = [] + + self.shng_version = Version.format(version.split('-')[0]) + l = sys.version_info + self.py_version = Version.format(str(l[0])+'.'+str(l[1])+'.'+str(l[2])) + self.sh_basedir = os.sep.join(os.path.realpath(__file__).split(os.sep)[:-2]) self.logger.debug("Requirements_files is using '{}' as base directory".format(self.sh_basedir)) return @@ -1107,7 +1119,7 @@ def _build_packagelist(self, requirements): # build list of package requirement dicts packagelist = [] - self.logger.debug("Req_files: _build_packagelist: requirements = '{}'".format(requirements)) + self.logger.debug("_build_packagelist: Req_files: requirements = '{}'".format(requirements)) for key in requirements: packaged = {} wrk = re.split('<|>|=', key) @@ -1158,11 +1170,50 @@ def _build_packagelist(self, requirements): return packagelist + def _test_plugin_versions(self, filename): + + metaname = filename[:-16] + 'plugin.yaml' + meta = shyaml.yaml_load(metaname, ignore_notfound=True) + if meta is None: + #self.logger.warning(f"{metaname} not found") + return False, '' + else: + sh_minversion = Version.format(meta['plugin'].get('sh_minversion', '')) + sh_maxversion = Version.format(meta['plugin'].get('sh_maxversion', '')) + py_minversion = Version.format(meta['plugin'].get('py_minversion', '')) + py_maxversion = Version.format(meta['plugin'].get('py_maxversion', '')) + if sh_minversion != '': + self.logger.debug(f"sh_minversion={sh_minversion}, self.shng_version={self.shng_version}") + if Version.compare(sh_minversion, self.shng_version, '>'): + self.logger.debug(f"meta data: {metaname} - meta: sh_minversion={sh_minversion}, sh_maxversion={sh_maxversion}") + return False, 'SmartHomeNG min version' + if sh_maxversion != '': + self.logger.debug(f"sh_maxversion={sh_maxversion}, self.shng_version={self.shng_version}") + if Version.compare(sh_maxversion, self.shng_version, '<'): + self.logger.debug(f"meta data: {metaname} - meta: sh_minversion={sh_minversion}, sh_maxversion={sh_maxversion}") + return False, 'SmartHomeNG max version' + + if py_minversion != '': + self.logger.debug(f"py_minversion={py_minversion}, self.py_version={self.py_version}") + if Version.compare(py_minversion, self.py_version, '>'): + self.logger.debug(f"meta data: {metaname} - meta: py_minversion={py_minversion}, py_maxversion={py_maxversion}") + return False, 'Python min version' + if py_maxversion != '': + self.logger.debug(f"py_maxversion={py_maxversion}, self.py_version={self.py_version}") + if Version.compare(py_maxversion, self.py_version, '<'): + self.logger.debug(f"meta data: {metaname} - meta: py_minversion={py_minversion}, py_maxversion={py_maxversion}") + return False, 'Python max version' + #if sh_minversion != '' or sh_maxversion != '' or py_minversion != '' or py_maxversion != '': + # self.logger.warning(f"meta data: {metaname} - meta: sh_minversion={sh_minversion}, sh_maxversion={sh_maxversion}, py_minversion={py_minversion}, py_maxversion={py_maxversion}") + return True, '' + + def _get_filelist(self, selection): """ returns a list of files with all paths for a requirements.txt within a certain selection subpath. Currently selection is one of 'modules', 'lib', 'plugins' + When testing with travis the self.sh_basedir will be something like "/home/travis/build//smarthome" On a native Linux without specialities this is normally "/usr/local/smarthome" When inspecting the plugins then all requirements.txt for previous plugin versions should be omitted from the list. @@ -1176,15 +1227,22 @@ def _get_filelist(self, selection): #self.logger.debug("level = {}: root = {}".format(level, root)) if (selection == "plugins" and "_pv" not in root) or (level < basedir_level + 3): for filename in fnmatch.filter(filenames, 'requirements.txt'): - # print("level = {}: root = {}".format(level, root)) - file_list.append(os.path.join(root, filename)) - self.logger.debug("found '{}'".format(os.path.join(root, filename))) + use_it = True + if (selection == "plugins"): + use_it, msg = self._test_plugin_versions(os.path.join(root, filename)) + if use_it: + # print("level = {}: root = {}".format(level, root)) + file_list.append(os.path.join(root, filename)) + self.logger.debug("found '{}'".format(os.path.join(root, filename))) + else: + self.logger.info(f"Not appended: ({msg}) all: {root}") self.logger.debug("_get_filelist found '{}'".format(file_list)) return file_list def _build_filelists(self, selection): + self.logger.debug(f"_build_filelists: selection={selection}") # global _module_files, _plugin_files, _core_files self._module_files = [] self._plugin_files = [] @@ -1194,7 +1252,7 @@ def _build_filelists(self, selection): # build list of all modules with requirements self._module_files = self._get_filelist('modules') - if selection in ['plugins','all']: + if selection in ['plugins', 'all']: # build list of all plugins with requirements self._plugin_files = self._get_filelist('plugins') @@ -1230,18 +1288,22 @@ def __read_requirementfiles(self): requirements = {} # Read requirements for core + #self.logger.warning(f"__read_requirementfiles: self._core_files={self._core_files}") for fname in self._core_files: self._read_requirementfile(fname, requirements, 'SmartHomeNG-') # Read requirements for modules + #self.logger.warning(f"__read_requirementfiles: self._module_files={self._module_files}") for fname in self._module_files: self._read_requirementfile(fname, requirements, 'SmartHomeNG-module ') # Read requirements for plugins + #self.logger.warning(f"__read_requirementfiles: self._plugin_files={self._plugin_files}") for fname in self._plugin_files: self._read_requirementfile(fname, requirements, 'plugin ') # Read requirements for configured plugins + #self.logger.warning(f"__read_requirementfiles: self._conf_plugin_files={self._conf_plugin_files}") for fname in self._conf_plugin_files: self._read_requirementfile(fname, requirements, 'configured plugin ') @@ -1366,6 +1428,7 @@ def _write_resultfile(self, selection, packagelist_consolidated, requirements): def set_conf_plugin_files(self, conf_plugin_filelist): self._conf_plugin_files = conf_plugin_filelist + def create_requirementsfile(self, selection): """ Ths method creates a requirements-file for one of the following selections: diff --git a/lib/utils.py b/lib/utils.py index 197115abb9..036ccbc092 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -562,6 +562,113 @@ def execute_subprocess(commandline, wait=True): p.wait() return (str(result, encoding='utf-8', errors='strict'), str(err, encoding='utf-8', errors='strict')) +#-------------------------------------------------------------------------------------------- + +class Version(): + + @staticmethod + def check_list(versl): + + while len(versl) < 4: + if isinstance(versl[0], str): + versl.append('0') + else: + versl.append(0) + while len(versl) > 4: + del versl[-1] + return versl + + @classmethod + def to_list(cls, vers): + """ + Split version number to list and get rid of non-numeric parts + + :param vers: + + :return: version as list + :rtype: list + """ + if len(vers) == 0: + vers = '0' + if vers[0].lower() == 'v': + vers = vers[1:] + + # create list with [major,minor,revision,build] + vsplit = vers.split('.') + vsplit = cls.check_list(vsplit) + + # get rid of non numeric parts + vlist = [] + build = 0 + if vsplit == '': + return '' + for v in vsplit: + if v[-1].isalpha(): + build += ord(v[-1].lower()) - 96 + v = v[:-1] + vi = 0 + try: + vi = int(v) + except: + pass + vlist.append(vi) + vlist[3] += build + return vlist + + @classmethod + def to_string(cls, versl): + + if versl == [0, 0, 0, 0]: + return '' + import copy + versl2 = copy.deepcopy(versl) + cls.check_list(versl2) + if versl2 == '': + return '' + + if versl2[3] == 0: + del versl2[3] + versls = [str(int) for int in versl2] + vers = ".".join(versls) + + return 'v' + vers + + @classmethod + def format(cls, vers): + + return cls.to_string(cls.to_list(str(vers))) + + @classmethod + def compare(cls, v1, v2, operator): + """ + Compare two version numbers and return if the condition is met + + :param v1: + :param v2: + :param operator: + :type v1: str or list of int + :type v2: str or list of int + :type operator: str + + :return: true if condition is met + :rtype: bool + """ + if isinstance(v1, str): + v1 = cls.to_list(v1) + if isinstance(v2, str): + v2 = cls.to_list(v2) + + result = False + if v1 == v2 and operator in ['>=', '==', '<=']: + result = True + if v1 < v2 and operator in ['<', '<=']: + result = True + if v1 > v2 and operator in ['>', '>=']: + result = True + # logger.warning(f"_compare_versions: v1={v1}, v2={v2}, operator='{operator}', result={result}") + return result + +#-------------------------------------------------------------------------------------------- def get_python_version(): PYTHON_VERSION = str(sys.version_info[0]) + '.' + str(sys.version_info[1]) + '.' + str(sys.version_info[2]) + ' ' + str(sys.version_info[3]) From f7bfc3b379411e2d6b6c2d422b352c7f8a1fad03 Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 3 Jan 2022 11:46:32 +0100 Subject: [PATCH 19/95] lib.shpypi: Small fix --- lib/shpypi.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/shpypi.py b/lib/shpypi.py index 093e1cce95..f287f24a6a 100644 --- a/lib/shpypi.py +++ b/lib/shpypi.py @@ -1181,7 +1181,8 @@ def _test_plugin_versions(self, filename): sh_minversion = Version.format(meta['plugin'].get('sh_minversion', '')) sh_maxversion = Version.format(meta['plugin'].get('sh_maxversion', '')) py_minversion = Version.format(meta['plugin'].get('py_minversion', '')) - py_maxversion = Version.format(meta['plugin'].get('py_maxversion', '')) + #py_maxversion = Version.format(meta['plugin'].get('py_maxversion', '')) + py_maxversion = meta['plugin'].get('py_maxversion', '') #w/o format to enable max 3.8 -> 3.8.999 if sh_minversion != '': self.logger.debug(f"sh_minversion={sh_minversion}, self.shng_version={self.shng_version}") if Version.compare(sh_minversion, self.shng_version, '>'): @@ -1199,6 +1200,12 @@ def _test_plugin_versions(self, filename): self.logger.debug(f"meta data: {metaname} - meta: py_minversion={py_minversion}, py_maxversion={py_maxversion}") return False, 'Python min version' if py_maxversion != '': + max = str(py_maxversion) + if len(max.split('.')) == 2: + # if given max version has only two parts, make it the max for that version: 3.8 -> 3.8.999 + max += '.999' + py_maxversion = Version.format(str(max)) + self.logger.debug(f"py_maxversion={py_maxversion}, self.py_version={self.py_version}") if Version.compare(py_maxversion, self.py_version, '<'): self.logger.debug(f"meta data: {metaname} - meta: py_minversion={py_minversion}, py_maxversion={py_maxversion}") From 14d7e91f472f015d9d315a908326f9c2d1f7b3e9 Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 3 Jan 2022 11:49:56 +0100 Subject: [PATCH 20/95] lib.shpypi: Another small fix --- lib/shpypi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/shpypi.py b/lib/shpypi.py index f287f24a6a..d5f13ccd0c 100644 --- a/lib/shpypi.py +++ b/lib/shpypi.py @@ -1067,7 +1067,7 @@ class Requirements_files(): _plugin_files = [] _core_files = [] # to be a list in the future - def __init__(self, version=None): + def __init__(self, version='0.0.0'): self.logger = logging.getLogger(__name__) From 706806e1953f900d20349bcba9c43eee4130f69b Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 3 Jan 2022 12:37:51 +0100 Subject: [PATCH 21/95] lib.shpypi / tools: Some fixes --- lib/shpypi.py | 12 ++++++++---- tools/build_requirements.py | 19 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/shpypi.py b/lib/shpypi.py index d5f13ccd0c..afb72f5974 100644 --- a/lib/shpypi.py +++ b/lib/shpypi.py @@ -57,7 +57,7 @@ class Shpypi: - def __init__(self, sh=None, base=None, version=None): + def __init__(self, sh=None, base=None, version=None, for_tests=False): """ :param smarthome: @@ -65,6 +65,7 @@ def __init__(self, sh=None, base=None, version=None): NOTE: During initialization (and initial calls to some methods, the logging interface is not yet initialized!!! """ self.logger = logging.getLogger(__name__) + self.for_tests = for_tests global _shpypi_instance if _shpypi_instance is not None: @@ -74,7 +75,7 @@ def __init__(self, sh=None, base=None, version=None): self.logger.critical("A second 'shpypi' object has been created. There should only be ONE instance of class 'Shpypi'!!! Called from: {} {} ({})".format(calframe[1][1], calframe[1][2], calframe[1][3])) _shpypi_instance = self - self.req_files = Requirements_files(version) + self.req_files = Requirements_files(version, self.for_tests) self.scheduler_crontab = ['init', '7 3 * *'] @@ -1067,10 +1068,11 @@ class Requirements_files(): _plugin_files = [] _core_files = [] # to be a list in the future - def __init__(self, version='0.0.0'): + def __init__(self, version='0.0.0', for_tests=False): self.logger = logging.getLogger(__name__) + self.for_tests = for_tests self._conf_plugin_files = [] self.shng_version = Version.format(version.split('-')[0]) @@ -1235,8 +1237,10 @@ def _get_filelist(self, selection): if (selection == "plugins" and "_pv" not in root) or (level < basedir_level + 3): for filename in fnmatch.filter(filenames, 'requirements.txt'): use_it = True + msg = '' if (selection == "plugins"): - use_it, msg = self._test_plugin_versions(os.path.join(root, filename)) + if self.for_tests: + use_it, msg = self._test_plugin_versions(os.path.join(root, filename)) if use_it: # print("level = {}: root = {}".format(level, root)) file_list.append(os.path.join(root, filename)) diff --git a/tools/build_requirements.py b/tools/build_requirements.py index 7753e27670..846c41af17 100644 --- a/tools/build_requirements.py +++ b/tools/build_requirements.py @@ -22,12 +22,12 @@ """ This script assembles a complete list of requirements for the SmartHomeNG core and all plugins. -The list is not tested for correctness nor checked for contrary -requirements. +The list is not tested for correctness nor checked for contrary +requirements. The procedure is as following: 1) walks the plugins subdirectory and collect all files with requirements -2) read the requirements for the core +2) read the requirements for the core 3) read all files with requirements and add them with source of requirement to a dict 4) write it all to a file all.txt in requirements directory @@ -50,6 +50,13 @@ import lib.shpypi as shpypi +import bin.shngversion +VERSION = bin.shngversion.get_shng_version() + +from lib.shpypi import Shpypi +shpypi = Shpypi.get_instance() +if shpypi is None: + shpypi = Shpypi(base=sh_basedir, version=VERSION, for_tests=True) # ========================================================================== @@ -67,16 +74,16 @@ print ("Directory /requirements not found!") exit(1) -req_files = shpypi.Requirements_files() +#req_files = shpypi.req_files('1.9.0.1', for_tests=True) # req_files.create_requirementsfile('core') # print("File 'requirements" + os.sep + "core.txt' created.") # req_files.create_requirementsfile('modules') # print("File 'requirements" + os.sep + "modules.txt' created.") -fn = req_files.create_requirementsfile('base') +fn = shpypi.req_files.create_requirementsfile('base') print("File {} created.".format(fn)) # req_files.create_requirementsfile('plugins') # print("File 'requirements" + os.sep + "plugins.txt' created.") -fn = req_files.create_requirementsfile('all') +fn = shpypi.req_files.create_requirementsfile('all') print("File {} created.".format(fn)) From 623ed29e6fa1f1fb3969e8a37dd6a45289e42746 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Tue, 4 Jan 2022 12:47:08 +0100 Subject: [PATCH 22/95] lib.network: fix validate_inet_addr return for invalid ip --- lib/network.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index 8f885f61f2..8fa251a414 100644 --- a/lib/network.py +++ b/lib/network.py @@ -172,7 +172,7 @@ def validate_inet_addr(addr, port): :type addr: str :param port: port number under test :type port: num - :return: (ip_address, port, family) or (None, undef, undef) if error occurs + :return: (ip_address, port, family) or (None, 0, None) if error occurs :rtype: tuple """ logger = logging.getLogger(__name__) @@ -199,6 +199,8 @@ def validate_inet_addr(addr, port): # Unable to resolve hostname logger.error(f'Cannot resolve {addr} to a valid ip address (v4 or v6): {e}') ip = None + port = 0 + family = None return (ip, port, family) From 1cd8358575052584a2e10cbf92ca5d47b1a139c0 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Tue, 4 Jan 2022 12:55:28 +0100 Subject: [PATCH 23/95] lib.network: fix for clean shutdown if not connected --- lib/network.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/network.py b/lib/network.py index 8fa251a414..b61058ff3c 100644 --- a/lib/network.py +++ b/lib/network.py @@ -866,9 +866,10 @@ def close(self): """ Close the current client socket. """ - self.logger.info(f'Closing connection to {self._host} on TCP port {self._port}') self.__running = False - self._socket.shutdown(socket.SHUT_RD) + self.logger.info(f'Closing connection to {self._host} on TCP port {self._port}') + if self._is_connected: + self._socket.shutdown(socket.SHUT_RD) if self.__connect_thread is not None and self.__connect_thread.is_alive(): self.__connect_thread.join() if self.__receive_thread is not None and self.__receive_thread.is_alive(): From 1ada57fac7380713c478c026dbbfa6f02c9e38c2 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 4 Jan 2022 17:02:20 +0100 Subject: [PATCH 24/95] lib.item: introduce new property last_trigger(_by/_age) and prev_trigger(_by/_age) and according functions. This way it is possible to figure out what has triggered the eval expression. The property might be especially useful inside the eval expression itself but can also be used to figure out what, when (or if) an eval got triggered (without being updated, because maybe the eval didn't succeed or the value has not changed) --- doc/user/source/referenz/items/funktionen.rst | 59 +++++++--- doc/user/source/referenz/items/properties.rst | 34 +++++- .../items/standard_attribute/eval.rst | 32 ++++++ lib/item/item.py | 84 ++++++++++++++- lib/item/property.py | 102 ++++++++++++++++++ 5 files changed, 289 insertions(+), 22 deletions(-) diff --git a/doc/user/source/referenz/items/funktionen.rst b/doc/user/source/referenz/items/funktionen.rst index baca2bcef2..c7fb2c1d11 100644 --- a/doc/user/source/referenz/items/funktionen.rst +++ b/doc/user/source/referenz/items/funktionen.rst @@ -47,46 +47,67 @@ genutzt werden können. Weiterhin werden die folgenden Funktionen unterstützt, die jedoch **ab SmartHomeNG v1.6** nicht mehr genutzt werden sollen. -Bitte stattdessen die entsprechenden in SmartHomeNG v1.6 eingeführten Properties benutzen. +Bitte stattdessen die entsprechenden in SmartHomeNG v1.6 eingeführten :doc:`Properties ` benutzen. +------------------------+------------------------------------------------------------------------------+ | **Funktion** | **Beschreibung** | +========================+==============================================================================+ | id() | Liefert den item-Pfad des Items zurück. Aufruf: sh.item.id() | +------------------------+------------------------------------------------------------------------------+ +| () | Liefert den aktuellen Wert des Items zurück. Aufruf: sh.item() | ++------------------------+------------------------------------------------------------------------------+ +| prev_value() | Liefert den Wert des Items zurück, den es vor der letzten Änderung hatte. | ++------------------------+------------------------------------------------------------------------------+ | last_update() | Liefert ein *datetime* Objekt mit dem Zeitpunkt des letzten Updates des | | | Items zurück. Im Gegensatz zu **last_change()** wird dieser Zeitstempel auch | | | verändert, wenn sich bei einem Update der Wert des Items nicht ändert. | +------------------------+------------------------------------------------------------------------------+ +| prev_update() | Liefert ein *datetime* Objekt mit dem Zeitpunkt des vorletzten Updates des | +| | Items zurück. Im Gegensatz zu **prev_change()** wird dieser Zeitstempel auch | +| | verändert, wenn sich bei einem Update der Wert des Items nicht ändert. | ++------------------------+------------------------------------------------------------------------------+ | last_change() | Liefert ein *datetime* Objekt mit dem Zeitpunkt der letzten Änderung des | | | Items zurück. | +------------------------+------------------------------------------------------------------------------+ -| age() | Liefert das Alter des Items seit der letzten Änderung des Wertes in Sekunden | -| | zurück. | +| prev_change() | Liefert ein *datetime* Objekt mit dem Zeitpunkt der vorletzten Änderung des | +| | Items zurück. | ++------------------------+------------------------------------------------------------------------------+ +| last_trigger() | Liefert ein *datetime* Objekt mit dem Zeitpunkt der letzten eval Triggerung | +| | des Items zurück. Hat das Item kein eval Attribut oder wurde es nicht | +| | getriggert, wird der Zeitpunkt des letzten Updates übermittelt. | +| | (Neu **ab SmartHomeNG v1.9.1**) | ++------------------------+------------------------------------------------------------------------------+ +| prev_trigger() | Liefert ein *datetime* Objekt mit dem Zeitpunkt der vorletzten eval | +| | Triggerung des Items zurück. (Neu **ab SmartHomeNG v1.9.1**) | +------------------------+------------------------------------------------------------------------------+ | update_age() | Liefert das Alter des Items seit dem letzten Update in Sekunden zurück. Das | | | Update Age wird auch gesetzt, wenn sich bei einem Update der Wert des Items | | | nicht ändert. (Neu **ab SmartHomeNG v1.4**) | +------------------------+------------------------------------------------------------------------------+ -| changed_by() | Liefert einen String zurück, der auf das Objekt hinweist, welches das Item | -| | zuletzt geändert hat. | +| prev_update_age() | Liefert das Alter des vorangegangenen Updates in Sekunden zurück. | +------------------------+------------------------------------------------------------------------------+ -| updated_by() | Liefert einen String zurück, der auf das Objekt hinweist, durch welches | -| | das Item zuletzt ein Update erfahren hat. | +| age() | Liefert das Alter des Items seit der letzten Änderung des Wertes in Sekunden | +| | zurück. | +------------------------+------------------------------------------------------------------------------+ -| prev_value() | Liefert den Wert des Items zurück, den es vor der letzten Änderung hatte. | +| prev_age() | Liefert das Alter des vorangegangenen geänderten Wertes in Sekunden zurück. | +------------------------+------------------------------------------------------------------------------+ -| prev_update() | Liefert ein *datetime* Objekt mit dem Zeitpunkt des vorletzten Updates des | -| | Items zurück. Im Gegensatz zu **prev_change()** wird dieser Zeitstempel auch | -| | verändert, wenn sich bei einem Update der Wert des Items nicht ändert. | +| trigger_age() | Liefert das Alter der letzten Eval Triggerung in Sekunden zurück. | +| | (Neu **ab SmartHomeNG v1.9.1**) | +------------------------+------------------------------------------------------------------------------+ -| prev_change() | Liefert ein *datetime* Objekt mit dem Zeitpunkt der vorletzten Änderung des | -| | Items zurück. | +| prev_trigger_age() | Liefert das Alter der vorletzten Eval Triggerung in Sekunden zurück. | +| | (Neu **ab SmartHomeNG v1.9.1**) | +------------------------+------------------------------------------------------------------------------+ -| prev_update_age() | Liefert das Alter des vor-vorangegangenen Wertes in Sekunden zurück. | +| changed_by() | Liefert einen String zurück, der auf das Objekt hinweist, welches das Item | +| | zuletzt geändert hat. | +------------------------+------------------------------------------------------------------------------+ -| prev_age() | Liefert das Alter des vorangegangenen Wertes in Sekunden zurück. | +| updated_by() | Liefert einen String zurück, der auf das Objekt hinweist, durch welches | +| | das Item zuletzt ein Update erfahren hat. | +------------------------+------------------------------------------------------------------------------+ +| triggered_by() | Liefert einen String zurück, der auf das Objekt hinweist, durch welches | +| | eine eventuell vorhandene eval Expression getriggert wurde. | +| | (Neu **ab SmartHomeNG v1.9.1**) | ++------------------------+------------------------------------------------------------------------------+ + Beispiele für die Nutzung von Funktionen @@ -115,3 +136,11 @@ Die folgende Beispiel Logik nutzt einige der oben beschriebenen Funktionen: # will in- or decrement the living room light to 100 by a stepping of ``1`` and a timedelta of ``2.5`` seconds. sh.living.light.fade(100, 1, 2.5) + +Der folgende Beispiel eval Ausdruck sorgt dafür, dass ein Item den zugewiesenen Wert nur dann übernimmt, +wenn die Wertänderung bzw. das Anstoßen der eval Funktion über das Admin Interface erfolgt ist und das +letzte Update vor der aktuellen Triggerung über 10 Sekunden zurück liegt. + +.. code-block:: python + + eval: value if sh..self.triggered_by().startswith('admin') and sh..self.update_age() > 10 else None diff --git a/doc/user/source/referenz/items/properties.rst b/doc/user/source/referenz/items/properties.rst index f3788b75c6..dc6a8bab15 100644 --- a/doc/user/source/referenz/items/properties.rst +++ b/doc/user/source/referenz/items/properties.rst @@ -13,6 +13,7 @@ genutzt werden können. Alle Properties sind zumindest lesend (r/o) zugreifbar. auch beschrieben (r/w) werden. Properties sind **ab SmartHomeNG v1.6** verfügbar. +Die Properties ``last/prev_trigger(_by/_age)`` sind **ab SmartHomeNG v1.9.1** verfügbar. Properties werden in Logiken und eval-Ausdrücken folgendermaßen abgerufen: @@ -61,6 +62,15 @@ Werte für Properties, die auch geschrieben werden können (z.B. in Logiken), we | last_change_by | r/o | str | Liefert einen String zurück, der auf das Objekt hinweist, welches das Item | | | | | zuletzt geändert hat. | +----------------------+------------+----------+------------------------------------------------------------------------------+ +| last_trigger | r/o | datetime | Liefert ein *datetime* Objekt mit dem Zeitpunkt der letzten eval Triggerung | +| | | | des Items zurück. Hat das Item kein eval Attribut oder wurde es nicht | +| | | | getriggert, wird der Zeitpunkt des letzten Updates übermittelt. | ++----------------------+------------+----------+------------------------------------------------------------------------------+ +| last_trigger_age | r/o | float | Liefert das Alter der letzten Eval Triggerung in Sekunden zurück. | ++----------------------+------------+----------+------------------------------------------------------------------------------+ +| last_trigger_by | r/o | str | Liefert einen String zurück, der auf das Objekt hinweist, durch welches | +| | | | eine eventuell vorhandene eval Expression getriggert wurde. | ++----------------------+------------+----------+------------------------------------------------------------------------------+ | last_update | r/o | datetime | Liefert ein *datetime* Objekt mit dem Zeitpunkt des letzten Updates des | | | | | Items zurück. Im Gegensatz zu **last_change()** wird dieser Zeitstempel auch | | | | | verändert, wenn sich bei einem Update der Wert des Items nicht ändert. | @@ -95,19 +105,27 @@ Werte für Properties, die auch geschrieben werden können (z.B. in Logiken), we | prev_change | r/o | datetime | Liefert ein *datetime* Objekt mit dem Zeitpunkt der vorletzten Änderung des | | | | | Items zurück. | +----------------------+------------+----------+------------------------------------------------------------------------------+ -| prev_change_age | r/o | float | Liefert das Alter des vorangegangenen Wertes in Sekunden zurück. | +| prev_change_age | r/o | float | Liefert das Alter des vorangegangenen (geänderten) Wertes in Sekunden zurück.| +----------------------+------------+----------+------------------------------------------------------------------------------+ | prev_change_by | r/o | str | Liefert einen String zurück, der auf das Objekt hinweist, welches das Item | -| | | | das vorletzte mal geändert hat. | +| | | | das vorletzte Mal geändert hat. | ++----------------------+------------+----------+------------------------------------------------------------------------------+ +| prev_trigger | r/o | datetime | Liefert ein *datetime* Objekt mit dem Zeitpunkt der vorletzten eval | +| | | | Triggerung des Items zurück. | ++----------------------+------------+----------+------------------------------------------------------------------------------+ +| prev_trigger_age | r/o | float | Liefert das Alter der vorletzten Eval Triggerung in Sekunden zurück. | ++----------------------+------------+----------+------------------------------------------------------------------------------+ +| prev_trigger_by | r/o | str | Liefert einen String zurück, der auf das Objekt hinweist, durch welches | +| | | | eine eventuell vorhandene eval Expression das vorletzte Mal ausgelöst wurde. | +----------------------+------------+----------+------------------------------------------------------------------------------+ | prev_update | r/o | datetime | Liefert ein *datetime* Objekt mit dem Zeitpunkt des vorletzten Updates des | | | | | Items zurück. Im Gegensatz zu **prev_change()** wird dieser Zeitstempel auch | | | | | verändert, wenn sich bei einem Update der Wert des Items nicht ändert. | +----------------------+------------+----------+------------------------------------------------------------------------------+ -| prev_update_age | r/o | float | Liefert das Alter des vor-vorangegangenen Wertes in Sekunden zurück. | +| prev_update_age | r/o | float | Liefert das Alter des vorangegangenen Updates in Sekunden zurück. | +----------------------+------------+----------+------------------------------------------------------------------------------+ | prev_update_by | r/o | str | Liefert einen String zurück, der auf das Objekt hinweist, durch welches das | -| | | | Item das vorletzte mal ein Update erfahren hat. | +| | | | Item das vorletzte Mal ein Update erfahren hat. | +----------------------+------------+----------+------------------------------------------------------------------------------+ | prev_value | r/o | str | Liefert den Wert des Items zurück, den es vor der vorletzten Änderung hatte. | +----------------------+------------+----------+------------------------------------------------------------------------------+ @@ -129,3 +147,11 @@ Werte für Properties, die auch geschrieben werden können (z.B. in Logiken), we | | | | **schaltaktor** zugegriffen werden: **schaltaktor.property.knx_send** | | | | | Dyn. Properties sind erst in SmartHomeNG Releases nach v1.6.1 implementiert. | +----------------------+------------+----------+------------------------------------------------------------------------------+ + +Der folgende Beispiel eval Ausdruck sorgt dafür, dass ein Item den zugewiesenen Wert nur dann übernimmt, +wenn die Wertänderung bzw. das Anstoßen der eval Funktion über das Admin Interface erfolgt ist und das +letzte Update vor der aktuellen Triggerung über 10 Sekunden zurück liegt. + +.. code-block:: python + + eval: value if sh..self.property.last_trigger_by == 'admin' and sh..self.property.last_update_age > 10 else None diff --git a/doc/user/source/referenz/items/standard_attribute/eval.rst b/doc/user/source/referenz/items/standard_attribute/eval.rst index 7df35d8287..3d3ca967a5 100644 --- a/doc/user/source/referenz/items/standard_attribute/eval.rst +++ b/doc/user/source/referenz/items/standard_attribute/eval.rst @@ -255,3 +255,35 @@ den Ausdruck **sh..self.prev_value()** zugegriffen werden. **None** bewirkt, dass das Item unverändert bleibt, und somit auch keine Trigger ausgelöst werden. Diese Art, per ``None`` Werte nicht zuzuweisen, funktioniert **nur** bei ``eval``; bei anderen Attributen wie z.B. ``cycle`` kann dies nicht genutzt werden. + + +Abfrage des Auslösers eines **eval** Ausdrucks +---------------------------------------------- + +Zu dem Zeitpunkt, wo ein **eval** Ausdruck ausgeführt wird, bezieht sich das Property ``last_update_by`` **nicht** auf +die aktuellste Auslösung des Items, sondern auf das letzte Update davor. Möchte man nun aber abfragen, durch welches +Item, Plugin oder durch welche Logik die Ausführung des eval Ausdrucks angestoßen wurde, +muss das seit SmartHomeNG 1.9.1 implementierte Property ``last_trigger_by`` genutzt werden. + +Dabei findet folgender zeitlicher Ablauf statt: + + - Aktualisieren des Itemwerts oder Veränderung eines Triggeritems (eval_trigger) + - Setzen und Aktualisieren der ``last_trigger_by`` und dazu passenden Properties + - Ausführen des eval Ausdrucks + - Ist das Ergebnis des eval Ausdrucks **None**, passiert nichts weiter. Das Item wird nicht aktualisiert, + ``last_update_by`` ändert sich ebenfalls nicht. + - Ist das Ergebnis des eval Ausdrucks ein erlaubter Wert, wird dieser ins Item geschrieben. + ``last_update_by`` und/oder ``last_change_by`` werden entsprechend aktualisiert. + +Details zu den verschiedenen Abfragemöglichkeiten sind unter :doc:`Properties ` zu finden. + +.. attention:: + + Möchte man in einem **eval** Ausdruck abfragen, wodurch der Ausdruck ausgelöst wurde, muss **property.last_trigger_by** + genutzt werden. Das **property.last_update_by** wird erst danach (oder bei None gar nicht) aktualisiert. + +.. tip:: + + War die Auflösung eines **eval** Ausdrucks nicht erfolgreich (weil z.B. die Syntax falsch ist oder ein Item nicht + gefunden werden konnte), wird beim ``last_trigger_by`` den Caller- und Source-Angaben noch ein None (als "dest") hinten + angehängt. Der Wert beinhaltet somit ``::None``. diff --git a/lib/item/item.py b/lib/item/item.py index cf4ebcff36..44b500aebb 100644 --- a/lib/item/item.py +++ b/lib/item/item.py @@ -111,6 +111,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): self.cast = cast_bool self.__changed_by = 'Init:None' self.__updated_by = self.__changed_by + self.__triggered_by = 'N/A' self.__children = [] self.conf = {} self._crontab = None @@ -143,10 +144,13 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): self._items_to_trigger = [] self.__last_change = self.shtime.now() self.__last_update = self.__last_change + self.__last_trigger = self.__last_change self.__prev_change = self.__last_change self.__prev_update = self.__prev_change + self.__prev_trigger = self.__prev_change self.__prev_change_by = 'N/A' self.__prev_update_by = self.__prev_change_by + self.__prev_trigger_by = self.__prev_change_by self._lock = threading.Condition() self.__logics_to_trigger = [] self._name = path @@ -400,6 +404,7 @@ def __init__(self, smarthome, parent, path, config, items_instance=None): self.__last_change, self._value = cache_read(self._cache, self.shtime.tzinfo()) self.__prev_change = self.__last_change self.__updated_by = self.__changed_by + self.__triggered_by = 'N/A' self.__last_update = self.__last_change self.__prev_update = self.__prev_change @@ -648,6 +653,16 @@ def _get_last_update_age(self): delta = self.shtime.now() - self.__last_update return delta.total_seconds() + def _get_last_trigger(self): + return self.__last_trigger + + def _get_last_trigger_age(self): + delta = self.shtime.now() - self.__last_trigger + return delta.total_seconds() + + def _get_last_trigger_by(self): + return self.__triggered_by + def _get_last_value(self): return self.__last_value @@ -679,6 +694,18 @@ def _get_prev_update_by(self): def _get_prev_value(self): return self.__prev_value + def _get_prev_trigger(self): + return self.__prev_trigger + + def _get_prev_trigger_age(self): + + delta = self.__last_trigger - self.__prev_trigger + if delta.total_seconds() < 0.0001: + return 0.0 + return delta.total_seconds() + + def _get_prev_trigger_by(self): + return self. __prev_trigger_by """ @@ -748,6 +775,23 @@ def update_age(self): """ return self.property.last_update_age + def last_trigger(self): + """ + Timestamp of last trigger of item's eval expression (if available) + + :return: Timestamp of last update + """ + return self.property.last_trigger + + def trigger_age(self): + """ + Trigger-age of the item's last eval trigger. Returns the time in seconds since the eval has been triggered + + :return: Update-age of the value + :rtype: int + """ + return self.property.last_trigger_age + def prev_change(self): """ Timestamp of the previous (next-to-last) change of item's value @@ -783,6 +827,23 @@ def prev_update_age(self): """ return self.property.prev_update_age + def prev_trigger(self): + """ + Timestamp of previous (next-to-last) trigger of item's eval + + :return: Timestamp of previous update + """ + return self.property.prev_trigger + + def prev_trigger_age(self): + """ + Trigger-age of the item's previous eval trigger. Returns the time in seconds of the previous eval trigger + + :return: Update-age of the previous value + :rtype: int + """ + return self.property.prev_trigger_age + def prev_value(self): """ Next-to-last value of the item @@ -809,6 +870,14 @@ def updated_by(self): """ return self.property.last_update_by + def triggered_by(self): + """ + Returns an indication, which plugin, logic or event triggered the item's eval + + :return: Updater of item's value + :rtype: str + """ + return self.property.last_trigger_by """ Following are methods to handle relative item paths @@ -1188,7 +1257,7 @@ def __run_eval(self, value=None, caller='Eval', source=None, dest=None): cond = eval(self._trigger_condition) logger.warning("Item {}: Condition result '{}' evaluating trigger condition {}".format(self._path, cond, self._trigger_condition)) except Exception as e: - log_msg = "Item {}: Xroblem evaluating trigger condition '{}': {}".format(self._path, self._trigger_condition, e) + log_msg = "Item {}: Problem evaluating trigger condition '{}': {}".format(self._path, self._trigger_condition, e) if (self._sh.shng_status['code'] != 20) and (caller != 'Init'): logger.debug(log_msg) else: @@ -1209,9 +1278,17 @@ def __run_eval(self, value=None, caller='Eval', source=None, dest=None): # uf.import_user_modules() - Modules were loaded during initialization phase of shng try: - #logger.warning("Item {}: Evaluating item value {}".format(self._path, self._eval)) + self.__prev_trigger_by = self.__triggered_by + self.__triggered_by = "{0}:{1}".format(caller, source) + self.__prev_trigger = self.__last_trigger + self.__last_trigger = self.shtime.now() + + logger.debug("Item {}: Eval triggered by: {}. Evaluating item with value {}. Eval expression: {}".format(self._path, self.__triggered_by, value, self._eval)) value = eval(self._eval) except Exception as e: + # adding "None" as the "destination" information at end of triggered_by + # This helps figuring out whether an eval expression was successfully evaluated or not. + self.__triggered_by = "{0}:{1}:None".format(caller, source) log_msg = "Item {}: problem evaluating '{}': {}".format(self._path, self._eval, e) if (self._sh.shng_status['code'] != 20) and (caller != 'Init'): logger.debug(log_msg + " (status_code={}/caller={})".format(self._sh.shng_status['code'], caller)) @@ -1429,6 +1506,7 @@ def _set_value(self, value, caller, source=None, dest=None, prev_change=None, la self.__prev_update_by = self.__updated_by self.__changed_by = "{0}:{1}".format(caller, source) self.__updated_by = "{0}:{1}".format(caller, source) + self.__triggered_by = "{0}:{1}".format(caller, source) if caller != "fader": # log every item change to standard logger, if level is DEBUG @@ -1557,7 +1635,7 @@ def get_method_triggers(self): return self.__methods_to_trigger - def timer(self, time, value, auto=False, compat=ATTRIB_COMPAT_DEFAULT, source=None): + def timer(self, time, value, auto=False, compat=ATTRIB_COMPAT_DEFAULT, caller=None, source=None): time = self._cast_duration(time) value = self._castvalue_to_itemtype(value, compat) if auto: diff --git a/lib/item/property.py b/lib/item/property.py index 1793af37d4..2b6f940649 100644 --- a/lib/item/property.py +++ b/lib/item/property.py @@ -311,6 +311,57 @@ def last_update_by(self, value): self._ro_error() return + @property + def last_trigger(self): + """ + Read-Only Property: last_trigger + + Available in SmartHomeNG v1.9.1 and above + + :return: path of the item + :rtype: str + """ + return self._item._get_last_trigger() + + @last_trigger.setter + def last_trigger(self, value): + self._ro_error() + return + + @property + def last_trigger_age(self): + """ + Read-Only Property: last_trigger_age + + Available in SmartHomeNG v1.9.1 and above + + :return: path of the item + :rtype: str + """ + return self._item._get_last_trigger_age() + + @last_trigger_age.setter + def last_trigger_age(self, value): + self._ro_error() + return + + @property + def last_trigger_by(self): + """ + Read-Only Property: last_trigger_by + + Available in SmartHomeNG v1.9.1 and above + + :return: path of the item + :rtype: str + """ + return self._item._get_last_trigger_by() + + @last_trigger_by.setter + def last_trigger_by(self, value): + self._ro_error() + return + @property def last_value(self): """ @@ -566,6 +617,57 @@ def prev_update_by(self, value): self._ro_error() return + @property + def prev_trigger(self): + """ + Read-Only Property: prev_trigger + + Available in SmartHomeNG v1.9.1 and above + + :return: path of the item + :rtype: str + """ + return self._item._get_prev_trigger() + + @prev_trigger.setter + def prev_trigger(self, value): + self._ro_error() + return + + @property + def prev_trigger_age(self): + """ + Read-Only Property: prev_trigger_age + + Available in SmartHomeNG v1.9.1 and above + + :return: path of the item + :rtype: str + """ + return self._item._get_prev_trigger_age() + + @prev_trigger_age.setter + def prev_trigger_age(self, value): + self._ro_error() + return + + @property + def prev_trigger_by(self): + """ + Read-Only Property: prev_trigger_by + + Available in SmartHomeNG v1.9.1 and above + + :return: path of the item + :rtype: str + """ + return self._item._get_prev_trigger_by() + + @prev_trigger_by.setter + def prev_trigger_by(self, value): + self._ro_error() + return + @property def prev_value(self): """ From f8c8a02d48da01b1ce3f81da0015715701c5f5b7 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Wed, 5 Jan 2022 01:42:38 +0100 Subject: [PATCH 25/95] get relative item path: fix issue where last character of expression part got truncated and therefore eval expression might get invalid --- lib/item/item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/item/item.py b/lib/item/item.py index cf4ebcff36..1a005141fa 100644 --- a/lib/item/item.py +++ b/lib/item/item.py @@ -926,7 +926,7 @@ def __checkfortags(evalstr, begintag, endtag): while (rest.find(begintag+'.') != -1): pref += rest[:rest.find(begintag+'.')+len(begintag)] rest = rest[rest.find(begintag+'.')+len(begintag):] - if endtag == '': + if endtag == '' or rest.find(endtag) == -1: rel = rest rest = '' else: From 1e9fbd3d141713d0ac23513ba4fab8c147679a2f Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 5 Jan 2022 16:51:06 +0100 Subject: [PATCH 26/95] lib.network: added __str__ methods to simplyfy logging --- lib/network.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index b61058ff3c..de355c2a38 100644 --- a/lib/network.py +++ b/lib/network.py @@ -875,6 +875,12 @@ def close(self): if self.__receive_thread is not None and self.__receive_thread.is_alive(): self.__receive_thread.join() + def __str__(self): + if self.name: + return self.name + else: + return super().__str__() + class ConnectionClient(object): """ @@ -1004,6 +1010,12 @@ def _iac_to_string(self, msg): string += chr(char) return string.rstrip() + def __str__(self): + if self.name: + return self.name + else: + return super().__str__() + class Tcp_server(object): """ @@ -1247,6 +1259,12 @@ def close(self): self.__listening_thread.join() self.__loop.close() + def __str__(self): + if self.name: + return self.name + else: + return super().__str__() + class Udp_server(object): """ @@ -1434,4 +1452,10 @@ async def __handle_connection(self, data, addr): except UnicodeError: self.logger.debug(f'Received undecodable bytes from {host}:{port}') else: - self.logger.debug(f'Received empty datagram from {host}:{port}') \ No newline at end of file + self.logger.debug(f'Received empty datagram from {host}:{port}') + + def __str__(self): + if self.name: + return self.name + else: + return super().__str__() From 0a7da952d34b40612d740ffa8da4356ab71d64b0 Mon Sep 17 00:00:00 2001 From: msinn Date: Wed, 5 Jan 2022 18:27:13 +0100 Subject: [PATCH 27/95] lib.scheduler: Trigger dict in logics contains now the source (cron, cycle) for scheduler triggers. --- lib/scheduler.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index 03c4c86855..ee2248516a 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -285,7 +285,8 @@ def run(self): if task['next'] is not None: if task['next'] < now: self._runc.acquire() - self._runq.insert(task['prio'], (name, task['obj'], 'Scheduler', None, None, task['value'])) + # insert priority and a tuple of (name, obj, by, source, dest, value) # ms + self._runq.insert(task['prio'], (name, task['obj'], 'Scheduler', task.get('source', None), None, task['value'])) self._runc.notify() self._runc.release() task['next'] = None @@ -415,9 +416,11 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, self.items = Items.get_instance() self._lock.acquire() try: + source = '??' if isinstance(cron, str): cron = [cron, ] if isinstance(cron, list): + details = None _cron = {} for entry in cron: desc, __, _value = entry.partition('=') @@ -427,6 +430,7 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, else: _value = _value.strip() if desc.lower().startswith('init'): + details = desc offset = 5 # default init offset desc, op, seconds = desc.partition('+') if op: @@ -439,12 +443,14 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, next = self.shtime.now() + datetime.timedelta(seconds=offset) else: _cron[desc] = _value + source = {'source': 'cron', 'details': details} if _cron == {}: cron = None else: cron = _cron if isinstance(cycle, int): - cycle = {cycle: None} + source = {'source': 'cycle1', 'details': cycle} + cycle = {cycle: cycle} elif isinstance(cycle, str): cycle, __, _value = cycle.partition('=') try: @@ -455,10 +461,11 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, if _value != '': _value = _value.strip() else: - _value = None + _value = cycle cycle = {cycle: _value} + source = {'source': 'cycle', 'details': _value} if cycle is not None and offset is None: # spread cycle jobs - offset = random.randint(10, 15) + offset = random.randint(10, 15) # change name for multi instance plugins if obj.__class__.__name__ == 'method': if isinstance(obj.__self__, SmartPlugin): @@ -467,7 +474,7 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, if not from_smartplugin: name = name +'_'+ obj.__self__.get_instance_name() logger.debug("Scheduler: Name changed by adding plugin instance name to: " + name) - self._scheduler[name] = {'prio': prio, 'obj': obj, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True} + self._scheduler[name] = {'prio': prio, 'obj': obj, 'source': source, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True} if next is None: self._next_time(name, offset) finally: @@ -561,10 +568,12 @@ def _next_time(self, name, offset=None): now = now.replace(microsecond=0) if job['cycle'] is not None: cycle = list(job['cycle'].keys())[0] - value = job['cycle'][cycle] + #value = job['cycle'][cycle] if offset is None: offset = cycle next_time = now + datetime.timedelta(seconds=offset) + #job['source'] = 'cycle' + job['source'] = {'source': 'cycle', 'details': str(cycle)} if job['cron'] is not None: for entry in job['cron']: if entry == 'None': @@ -574,11 +583,13 @@ def _next_time(self, name, offset=None): if ct < next_time: next_time = ct value = job['cron'][entry] + #job['source'] = 'cron' # ms + job['source'] = {'source': 'cron1', 'details': str(entry)+' -> '+str(next_time)} else: next_time = ct value = job['cron'][entry] + job['source'] = {'source': 'cron2', 'details': str(entry)+' -> '+str(next_time)} self._scheduler[name]['next'] = next_time - self._scheduler[name]['value'] = value if name not in ['Connections', 'series', 'SQLite dump']: logger.debug("{0} next time: {1}".format(name, next_time)) @@ -617,7 +628,11 @@ def _task(self, name, obj, by, source, dest, value): source_details = None if isinstance(source, dict): source_details = source.get('details', '') - source = source.get('item', '') + src = source.get('item', '') + if src == '': + # get source ('cron' or 'cycle') + src = source.get('source', '') + source = src trigger = {'by': by, 'source': source, 'source_details': source_details, 'dest': dest, 'value': value} # noqa #following variables are assigned to be available during logic execution From 97493914710e1d1c3d3c7c5331c4e9a6e8cc2dec Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Wed, 5 Jan 2022 20:35:23 +0100 Subject: [PATCH 28/95] modified restart to quit in fg/interactive modes --- bin/smarthome.py | 1 - lib/smarthome.py | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/smarthome.py b/bin/smarthome.py index a3961ecf85..fef94e7360 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -120,7 +120,6 @@ python_bin = sys.executable if ' ' in python_bin: python_bin = '"'+python_bin+'"' -# TODO Morg: keep original command line arguments? # if we didn't change the working dir (yet), for example... # command = [python_bin] + sys.argv command = [python_bin, os.path.join(BASE, 'bin', 'smarthome.py')] diff --git a/lib/smarthome.py b/lib/smarthome.py index b19af36a08..faecfd7a89 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -374,7 +374,6 @@ def __init__(self, MODE, extern_conf_dir=''): ############################################################# # Check Time -# TODO Morg: why hardcoded check? any added value by this? -> remove? while datetime.date.today().isoformat() < '2016-03-16': # XXX update date time.sleep(5) self._logger.info("Waiting for updated time.") @@ -713,8 +712,11 @@ def restart(self, source=''): This method is used to restart the python interpreter and SmartHomeNG If SmartHomeNG was started in one of the foreground modes (-f, -i, -d), - this will break the behaviour and daemonize. + just quit and let the user restart manually. """ + if self._mode in ['foreground', 'debug', 'interactive']: + self.stop() + if self.shng_status['code'] == 30: self._logger.warning("Another RESTART is issued, while SmartHomeNG is restarting. Reason: "+source) else: @@ -726,8 +728,7 @@ def restart(self, source=''): python_bin = sys.executable if ' ' in python_bin: python_bin = '"'+python_bin+'"' -# TODO Morg: add inofficial parameter to transport original MODE setting - command = python_bin + ' ' + os.path.join(self._base_dir, 'bin', 'smarthome.py') + ' -r' + command = python_bin + ' ' + os.path.join(self._base_dir, 'bin', 'smarthome.py') self._logger.info("Restart command = '{}'".format(command)) try: p = subprocess.Popen(command, shell=True) From ab69b19268a2c83554b65d8c18ab3635eec0d5fd Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Wed, 5 Jan 2022 20:38:49 +0100 Subject: [PATCH 29/95] lib.smarthome: change getbasedir for user function to avoid deprecated warning --- lib/smarthome.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/smarthome.py b/lib/smarthome.py index 6dd811422a..c6b810b47a 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -579,7 +579,7 @@ def start(self): ############################################################# # Init and import user-functions ############################################################# - uf.init_lib(self.getBaseDir()) + uf.init_lib(self.get_basedir()) ############################################################# # Init Item-Wrapper @@ -1210,4 +1210,3 @@ def runtime(self): self._deprecated_warning('Shtime-API') return self.shtime.runtime() - From abec0da201c9228e2abd7897e5816a2a2a75d237 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 6 Jan 2022 22:13:57 +0100 Subject: [PATCH 30/95] datatables: update and add extensions: DataTables 1.11.3, ColReorder 1.5.5, FixedHeader 3.2.1, Responsive 2.2.9 --- .../gstatic/datatables/datatables.min.css | 15 +- .../gstatic/datatables/datatables.min.js | 468 ++++++++++++------ 2 files changed, 334 insertions(+), 149 deletions(-) diff --git a/modules/http/webif/gstatic/datatables/datatables.min.css b/modules/http/webif/gstatic/datatables/datatables.min.css index 4720d1866d..71b692d0de 100644 --- a/modules/http/webif/gstatic/datatables/datatables.min.css +++ b/modules/http/webif/gstatic/datatables/datatables.min.css @@ -4,10 +4,19 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.11.0 + * https://datatables.net/download/#dt/dt-1.11.3/cr-1.5.5/fh-3.2.1/r-2.2.9 * * Included libraries: - * DataTables 1.11.0 + * DataTables 1.11.3, ColReorder 1.5.5, FixedHeader 3.2.1, Responsive 2.2.9 */ -td.dt-control{background:url("https://www.datatables.net/examples/resources/details_open.png") no-repeat center center;cursor:pointer}tr.dt-hasChild td.dt-control{background:url("https://www.datatables.net/examples/resources/details_close.png") no-repeat center center}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("img/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("img/sort_asc.png") !important}table.dataTable thead .sorting_desc{background-image:url("img/sort_desc.png") !important}table.dataTable thead .sorting_asc_disabled{background-image:url("img/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("img/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#b0bed9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, white 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, white 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, white 0%, #dcdcdc 100%);background:-o-linear-gradient(top, white 0%, #dcdcdc 100%);background:linear-gradient(to bottom, white 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));background:-webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} +td.dt-control{background:url("img/details_open.png") no-repeat center center;cursor:pointer}tr.dt-hasChild td.dt-control{background:url("img/details_close.png") no-repeat center center}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("img/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("img/sort_asc.png") !important}table.dataTable thead .sorting_desc{background-image:url("img/sort_desc.png") !important}table.dataTable thead .sorting_asc_disabled{background-image:url("img/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("img/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#b0bed9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, white 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, white 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, white 0%, #dcdcdc 100%);background:-o-linear-gradient(top, white 0%, #dcdcdc 100%);background:linear-gradient(to bottom, white 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));background:-webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} + + +table.DTCR_clonedTable.dataTable{position:absolute !important;background-color:rgba(255, 255, 255, 0.7);z-index:202}div.DTCR_pointer{width:1px;background-color:#0259c4;z-index:201} + + +table.fixedHeader-floating{background-color:white}table.fixedHeader-floating.no-footer{border-bottom-width:0}table.fixedHeader-locked{position:absolute !important;background-color:white}@media print{table.fixedHeader-floating{display:none}} + + +table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty{cursor:default !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty:before{display:none !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control{position:relative;padding-left:30px;cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before{top:50%;left:5px;height:1em;width:1em;margin-top:-9px;display:block;position:absolute;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before{content:"-";background-color:#d33333}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th.dtr-control{padding-left:27px}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td.dtr-control:before,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th.dtr-control:before{left:4px;height:14px;width:14px;border-radius:14px;line-height:14px;text-indent:3px}table.dataTable.dtr-column>tbody>tr>td.dtr-control,table.dataTable.dtr-column>tbody>tr>th.dtr-control,table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{position:relative;cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.dtr-control:before,table.dataTable.dtr-column>tbody>tr>th.dtr-control:before,table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{top:50%;left:50%;height:.8em;width:.8em;margin-top:-0.5em;margin-left:-0.5em;display:block;position:absolute;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable.dtr-column>tbody>tr.parent td.dtr-control:before,table.dataTable.dtr-column>tbody>tr.parent th.dtr-control:before,table.dataTable.dtr-column>tbody>tr.parent td.control:before,table.dataTable.dtr-column>tbody>tr.parent th.control:before{content:"-";background-color:#d33333}table.dataTable>tbody>tr.child{padding:.5em 1em}table.dataTable>tbody>tr.child:hover{background:transparent !important}table.dataTable>tbody>tr.child ul.dtr-details{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul.dtr-details>li{border-bottom:1px solid #efefef;padding:.5em 0}table.dataTable>tbody>tr.child ul.dtr-details>li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul.dtr-details>li:last-child{border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:bold}div.dtr-modal{position:fixed;box-sizing:border-box;top:0;left:0;height:100%;width:100%;z-index:100;padding:10em 1em}div.dtr-modal div.dtr-modal-display{position:absolute;top:0;left:0;bottom:0;right:0;width:50%;height:50%;overflow:auto;margin:auto;z-index:102;overflow:auto;background-color:#f5f5f7;border:1px solid black;border-radius:.5em;box-shadow:0 12px 30px rgba(0, 0, 0, 0.6)}div.dtr-modal div.dtr-modal-content{position:relative;padding:1em}div.dtr-modal div.dtr-modal-close{position:absolute;top:6px;right:6px;width:22px;height:22px;border:1px solid #eaeaea;background-color:#f9f9f9;text-align:center;border-radius:3px;cursor:pointer;z-index:12}div.dtr-modal div.dtr-modal-close:hover{background-color:#eaeaea}div.dtr-modal div.dtr-modal-background{position:fixed;top:0;left:0;right:0;bottom:0;z-index:101;background:rgba(0, 0, 0, 0.6)}@media screen and (max-width: 767px){div.dtr-modal div.dtr-modal-display{width:95%}} diff --git a/modules/http/webif/gstatic/datatables/datatables.min.js b/modules/http/webif/gstatic/datatables/datatables.min.js index 51b23cad18..f50e7f57b2 100644 --- a/modules/http/webif/gstatic/datatables/datatables.min.js +++ b/modules/http/webif/gstatic/datatables/datatables.min.js @@ -4,10 +4,10 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.11.0 + * https://datatables.net/download/#dt/dt-1.11.3/cr-1.5.5/fh-3.2.1/r-2.2.9 * * Included libraries: - * DataTables 1.11.0 + * DataTables 1.11.3, ColReorder 1.5.5, FixedHeader 3.2.1, Responsive 2.2.9 */ /*! @@ -21,7 +21,7 @@ or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. For details please refer to: http://www.datatables.net - DataTables 1.11.0 + DataTables 1.11.3 ©2008-2021 SpryMedia Ltd - datatables.net/license */ var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(l,z,A){l instanceof String&&(l=String(l));for(var q=l.length,E=0;E").css({position:"fixed",top:0,left:-1*l(z).scrollLeft(),height:1, +X(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&c!==a&&Wa(a)}}function zb(a){S(a,"ordering","bSort");S(a,"orderMulti","bSortMulti");S(a,"orderClasses","bSortClasses");S(a,"orderCellsTop","bSortCellsTop");S(a,"order","aaSorting");S(a,"orderFixed","aaSortingFixed");S(a,"paging","bPaginate");S(a,"pagingType","sPaginationType");S(a,"pageLength","iDisplayLength");S(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%": +"");"boolean"===typeof a.scrollX&&(a.scrollX=a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b").css({position:"fixed",top:0,left:-1*l(z).scrollLeft(),height:1, width:1,overflow:"hidden"}).append(l("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(l("
").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}l.extend(a.oBrowser,u.__browser);a.oScroll.iBarWidth=u.__browser.barWidth} -function Bb(a,b,c,d,e,h){var g=!1;if(c!==q){var f=c;g=!0}for(;d!==e;)a.hasOwnProperty(d)&&(f=g?b(f,a[d],d,a):a[d],g=!0,d+=h);return f}function Xa(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=l.extend({},u.models.oColumn,c,{nTh:b?b:A.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=l.extend({},u.models.oSearch,c[d]);Ga(a,d,l(b).data())}function Ga(a,b,c){b=a.aoColumns[b]; -var d=a.oClasses,e=l(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var h=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);h&&(b.sWidthOrig=h[1])}c!==q&&null!==c&&(zb(c),P(u.defaults.column,c,!0),c.mDataProp===q||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),l.extend(b,c),X(b,c,"sWidth","sWidthOrig"),c.iDataSort!==q&&(b.aDataSort=[c.iDataSort]),X(b,c,"aDataSort"));var g=b.mData,f=na(g), -k=b.mRender?na(b.mRender):null;c=function(n){return"string"===typeof n&&-1!==n.indexOf("@")};b._bAttrSrc=l.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(n,m,p){var t=f(n,m,q,p);return k&&m?k(t,m,n,p):t};b.fnSetData=function(n,m,p){return ha(g)(n,m,p)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==l.inArray("asc",b.asSorting);c=-1!==l.inArray("desc",b.asSorting);b.bSortable&&(a||c)?a&&!c? +function Cb(a,b,c,d,e,h){var f=!1;if(c!==q){var g=c;f=!0}for(;d!==e;)a.hasOwnProperty(d)&&(g=f?b(g,a[d],d,a):a[d],f=!0,d+=h);return g}function Xa(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=l.extend({},u.models.oColumn,c,{nTh:b?b:A.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=l.extend({},u.models.oSearch,c[d]);Ga(a,d,l(b).data())}function Ga(a,b,c){b=a.aoColumns[b]; +var d=a.oClasses,e=l(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var h=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);h&&(b.sWidthOrig=h[1])}c!==q&&null!==c&&(Ab(c),P(u.defaults.column,c,!0),c.mDataProp===q||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),l.extend(b,c),X(b,c,"sWidth","sWidthOrig"),c.iDataSort!==q&&(b.aDataSort=[c.iDataSort]),X(b,c,"aDataSort"));var f=b.mData,g=na(f), +k=b.mRender?na(b.mRender):null;c=function(m){return"string"===typeof m&&-1!==m.indexOf("@")};b._bAttrSrc=l.isPlainObject(f)&&(c(f.sort)||c(f.type)||c(f.filter));b._setter=null;b.fnGetData=function(m,n,p){var t=g(m,n,q,p);return k&&n?k(t,n,m,p):t};b.fnSetData=function(m,n,p){return ha(f)(m,n,p)};"number"!==typeof f&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==l.inArray("asc",b.asSorting);c=-1!==l.inArray("desc",b.asSorting);b.bSortable&&(a||c)?a&&!c? (b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function ta(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ya(a);for(var c=0,d=b.length;cn[m])d(f.length+n[m],k);else if("string"===typeof n[m]){var p=0;for(g=f.length;pb&&a[e]--; - -1!=d&&c===q&&a.splice(d,1)}function wa(a,b,c,d){var e=a.aoData[b],h,g=function(k,n){for(;k.childNodes.length;)k.removeChild(k.firstChild);k.innerHTML=T(a,b,n,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var f=e.anCells;if(f)if(d!==q)g(f[d],d);else for(c=0,h=f.length;c").appendTo(d));var k=0;for(b=f.length;k=a.fnRecordsDisplay()?0:f,a.iInitDisplayStart=-1);f=a._iDisplayStart;var m=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,V(a,!1);else if(!k)a.iDraw++;else if(!a.bDestroying&&!b){Fb(a);return}if(0!==n.length)for(b=k?a.aoData.length:m,g=k?0:f;g",{"class":h?e[0]:""}).append(l("",{valign:"top",colSpan:oa(a),"class":a.oClasses.sRowEmpty}).html(d))[0];F(a,"aoHeaderCallback","header",[l(a.nTHead).children("tr")[0],cb(a),f,m,n]);F(a,"aoFooterCallback","footer",[l(a.nTFoot).children("tr")[0],cb(a),f,m,n]);e=l(a.nTBody);e.children().detach(); -e.append(l(c));F(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ka(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Gb(a);d?za(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;ja(a);a._drawHold=!1}function Hb(a){var b=a.oClasses,c=l(a.nTable);c=l("
").insertBefore(c);var d=a.oFeatures,e=l("
",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0]; -a.nTableReinsertBefore=a.nTable.nextSibling;for(var h=a.sDom.split(""),g,f,k,n,m,p,t=0;t")[0];n=h[t+1];if("'"==n||'"'==n){m="";for(p=2;h[t+p]!=n;)m+=h[t+p],p++;"H"==m?m=b.sJUIHeader:"F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(n=m.split("."),k.id=n[0].substr(1,n[0].length-1),k.className=n[1]):"#"==m.charAt(0)?k.id=m.substr(1,m.length-1):k.className=m;t+=p}e.append(k);e=l(k)}else if(">"==f)e=e.parent();else if("l"==f&&d.bPaginate&&d.bLengthChange)g= -Ib(a);else if("f"==f&&d.bFilter)g=Jb(a);else if("r"==f&&d.bProcessing)g=Kb(a);else if("t"==f)g=Lb(a);else if("i"==f&&d.bInfo)g=Mb(a);else if("p"==f&&d.bPaginate)g=Nb(a);else if(0!==u.ext.feature.length)for(k=u.ext.feature,p=0,n=k.length;p',f=d.sSearch;f=f.match(/_INPUT_/)?f.replace("_INPUT_",g):f+g;b=l("
",{id:h.f?null:c+"_filter","class":b.sFilter}).append(l("
").addClass(b.sLength); -a.aanFeatures.l||(k[0].id=c+"_length");k.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));l("select",k).val(a._iDisplayLength).on("change.DT",function(n){jb(a,l(this).val());ja(a)});l(a.nTable).on("length.dt.DT",function(n,m,p){a===m&&l("select",k).val(p)});return k[0]}function Nb(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(g){ja(g)};b=l("
").addClass(a.oClasses.sPaging+b)[0];var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+ -"_paginate",a.aoDrawCallback.push({fn:function(g){if(d){var f=g._iDisplayStart,k=g._iDisplayLength,n=g.fnRecordsDisplay(),m=-1===k;f=m?0:Math.ceil(f/k);k=m?1:Math.ceil(n/k);n=c(f,k);var p;m=0;for(p=h.p.length;mh&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)): -"next"==b?d+e",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function V(a,b){a.oFeatures.bProcessing&&l(a.aanFeatures.r).css("display",b?"block":"none");F(a,null,"processing",[a,b])}function Lb(a){var b=l(a.nTable), -c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,h=a.oClasses,g=b.children("caption"),f=g.length?g[0]._captionSide:null,k=l(b[0].cloneNode(!1)),n=l(b[0].cloneNode(!1)),m=b.children("tfoot");m.length||(m=null);k=l("
",{"class":h.sScrollWrapper}).append(l("
",{"class":h.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?d?K(d):null:"100%"}).append(l("
",{"class":h.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(k.removeAttr("id").css("margin-left", -0).append("top"===f?g:null).append(b.children("thead"))))).append(l("
",{"class":h.sScrollBody}).css({position:"relative",overflow:"auto",width:d?K(d):null}).append(b));m&&k.append(l("
",{"class":h.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?K(d):null:"100%"}).append(l("
",{"class":h.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===f?g:null).append(b.children("tfoot")))));b=k.children();var p=b[0];h=b[1];var t=m?b[2]:null;if(d)l(h).on("scroll.DT", -function(v){v=this.scrollLeft;p.scrollLeft=v;m&&(t.scrollLeft=v)});l(h).css("max-height",e);c.bCollapse||l(h).css("height",e);a.nScrollHead=p;a.nScrollBody=h;a.nScrollFoot=t;a.aoDrawCallback.push({fn:Ha,sName:"scrolling"});return k[0]}function Ha(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY;b=b.iBarWidth;var h=l(a.nScrollHead),g=h[0].style,f=h.children("div"),k=f[0].style,n=f.children("table");f=a.nScrollBody;var m=l(f),p=f.style,t=l(a.nScrollFoot).children("div"),v=t.children("table"),x=l(a.nTHead), -w=l(a.nTable),r=w[0],C=r.style,G=a.nTFoot?l(a.nTFoot):null,aa=a.oBrowser,L=aa.bScrollOversize;U(a.aoColumns,"nTh");var O=[],I=[],H=[],ea=[],Y,Ca=function(D){D=D.style;D.paddingTop="0";D.paddingBottom="0";D.borderTopWidth="0";D.borderBottomWidth="0";D.height=0};var fa=f.scrollHeight>f.clientHeight;if(a.scrollBarVis!==fa&&a.scrollBarVis!==q)a.scrollBarVis=fa,ta(a);else{a.scrollBarVis=fa;w.children("thead, tfoot").remove();if(G){var ba=G.clone().prependTo(w);var la=G.find("tr");ba=ba.find("tr")}var mb= -x.clone().prependTo(w);x=x.find("tr");fa=mb.find("tr");mb.find("th, td").removeAttr("tabindex");c||(p.width="100%",h[0].style.width="100%");l.each(Na(a,mb),function(D,W){Y=ua(a,D);W.style.width=a.aoColumns[Y].sWidth});G&&ca(function(D){D.style.width=""},ba);h=w.outerWidth();""===c?(C.width="100%",L&&(w.find("tbody").height()>f.offsetHeight||"scroll"==m.css("overflow-y"))&&(C.width=K(w.outerWidth()-b)),h=w.outerWidth()):""!==d&&(C.width=K(d),h=w.outerWidth());ca(Ca,fa);ca(function(D){var W=z.getComputedStyle? -z.getComputedStyle(D).width:K(l(D).width());H.push(D.innerHTML);O.push(W)},fa);ca(function(D,W){D.style.width=O[W]},x);l(fa).height(0);G&&(ca(Ca,ba),ca(function(D){ea.push(D.innerHTML);I.push(K(l(D).css("width")))},ba),ca(function(D,W){D.style.width=I[W]},la),l(ba).height(0));ca(function(D,W){D.innerHTML='
'+H[W]+"
";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=O[W]},fa);G&&ca(function(D,W){D.innerHTML='
'+ -ea[W]+"
";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=I[W]},ba);w.outerWidth()f.offsetHeight||"scroll"==m.css("overflow-y")?h+b:h,L&&(f.scrollHeight>f.offsetHeight||"scroll"==m.css("overflow-y"))&&(C.width=K(la-b)),""!==c&&""===d||da(a,1,"Possible column misalignment",6)):la="100%";p.width=K(la);g.width=K(la);G&&(a.nScrollFoot.style.width=K(la));!e&&L&&(p.height=K(r.offsetHeight+b));c=w.outerWidth();n[0].style.width=K(c);k.width= -K(c);d=w.height()>f.clientHeight||"scroll"==m.css("overflow-y");e="padding"+(aa.bScrollbarLeft?"Left":"Right");k[e]=d?b+"px":"0px";G&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");w.children("colgroup").insertBefore(w.children("thead"));m.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(f.scrollTop=0)}}function ca(a,b,c){for(var d=0,e=0,h=b.length,g,f;e").appendTo(f.find("tbody"));f.find("thead, tfoot").remove();f.append(l(a.nTHead).clone()).append(l(a.nTFoot).clone());f.find("tfoot th, tfoot td").css("width","");n=Na(a,f.find("thead")[0]);for(v=0;v").css({width:w.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v= -0;v").css(h||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(f).appendTo(p);h&&g?f.width(g):h?(f.css("width","auto"),f.removeAttr("width"),f.width()").css("width",K(a)).appendTo(b||A.body);b=a[0].offsetWidth;a.remove();return b}function Zb(a,b){var c=$b(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]:l("").html(T(a,c,b,"display"))[0]}function $b(a,b){for(var c, -d=-1,e=-1,h=0,g=a.aoData.length;hd&&(d=c.length,e=h);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function pa(a){var b=[],c=a.aoColumns;var d=a.aaSortingFixed;var e=l.isPlainObject(d);var h=[];var g=function(m){m.length&&!Array.isArray(m[0])?h.push(m):l.merge(h,m)};Array.isArray(d)&&g(d);e&&d.pre&&g(d.pre);g(a.aaSorting);e&&d.post&&g(d.post);for(a=0;aG?1:0;if(0!==C)return"asc"===r.dir?C:-C}C=c[m];G=c[p];return CG?1:0}):g.sort(function(m,p){var t,v=f.length,x=e[m]._aSortData,w=e[p]._aSortData;for(t=0;tG?1:0})}a.bSorted=!0}function bc(a){var b=a.aoColumns, -c=pa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d/g,"");var k=h.nTh;k.removeAttribute("aria-sort");h.bSortable&&(0e?e+1:3))}e=0;for(h=d.length;ee?e+1:3))}a.aLastSort=d}function ac(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType], -e;d&&(e=d.call(a.oInstance,a,b,va(a,b)));for(var h,g=u.ext.type.order[c.sType+"-pre"],f=0,k=a.aoData.length;f=h.length?[0,m[1]]:m)}));f.search!==q&&l.extend(a.oPreviousSearch,Vb(f.search));if(f.columns)for(d=0,e=f.columns.length;d=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function fb(a,b){a=a.renderer;var c=u.ext.renderer[b];return l.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function Q(a){return a.oFeatures.bServerSide? -"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Da(a,b){var c=dc.numbers_length,d=Math.floor(c/2);b<=c?a=ra(0,b):a<=d?(a=ra(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=ra(b-(c-2),b):(a=ra(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function Wa(a){l.each({num:function(b){return Ta(b,a)},"num-fmt":function(b){return Ta(b,a,qb)},"html-num":function(b){return Ta(b,a,Ua)},"html-num-fmt":function(b){return Ta(b,a,Ua,qb)}},function(b, -c){M.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(M.type.search[b+a]=M.type.search.html)})}function ec(a){return function(){var b=[Sa(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a,b){if(this instanceof u)return l(a).DataTable(b);b=a;this.$=function(g,f){return this.api(!0).$(g,f)};this._=function(g,f){return this.api(!0).rows(g,f).data()};this.api=function(g){return g?new B(Sa(this[M.iApiIndex])):new B(this)};this.fnAddData= -function(g,f){var k=this.api(!0);g=Array.isArray(g)&&(Array.isArray(g[0])||l.isPlainObject(g[0]))?k.rows.add(g):k.row.add(g);(f===q||f)&&k.draw();return g.flatten().toArray()};this.fnAdjustColumnSizing=function(g){var f=this.api(!0).columns.adjust(),k=f.settings()[0],n=k.oScroll;g===q||g?f.draw(!1):(""!==n.sX||""!==n.sY)&&Ha(k)};this.fnClearTable=function(g){var f=this.api(!0).clear();(g===q||g)&&f.draw()};this.fnClose=function(g){this.api(!0).row(g).child.hide()};this.fnDeleteRow=function(g,f,k){var n= -this.api(!0);g=n.rows(g);var m=g.settings()[0],p=m.aoData[g[0][0]];g.remove();f&&f.call(this,m,p);(k===q||k)&&n.draw();return p};this.fnDestroy=function(g){this.api(!0).destroy(g)};this.fnDraw=function(g){this.api(!0).draw(g)};this.fnFilter=function(g,f,k,n,m,p){m=this.api(!0);null===f||f===q?m.search(g,k,n,p):m.column(f).search(g,k,n,p);m.draw()};this.fnGetData=function(g,f){var k=this.api(!0);if(g!==q){var n=g.nodeName?g.nodeName.toLowerCase():"";return f!==q||"td"==n||"th"==n?k.cell(g,f).data(): -k.row(g).data()||null}return k.data().toArray()};this.fnGetNodes=function(g){var f=this.api(!0);return g!==q?f.row(g).node():f.rows().nodes().flatten().toArray()};this.fnGetPosition=function(g){var f=this.api(!0),k=g.nodeName.toUpperCase();return"TR"==k?f.row(g).index():"TD"==k||"TH"==k?(g=f.cell(g).index(),[g.row,g.columnVisible,g.column]):null};this.fnIsOpen=function(g){return this.api(!0).row(g).child.isShown()};this.fnOpen=function(g,f,k){return this.api(!0).row(g).child(f,k).show().child()[0]}; -this.fnPageChange=function(g,f){g=this.api(!0).page(g);(f===q||f)&&g.draw(!1)};this.fnSetColumnVis=function(g,f,k){g=this.api(!0).column(g).visible(f);(k===q||k)&&g.columns.adjust().draw()};this.fnSettings=function(){return Sa(this[M.iApiIndex])};this.fnSort=function(g){this.api(!0).order(g).draw()};this.fnSortListener=function(g,f,k){this.api(!0).order.listener(g,f,k)};this.fnUpdate=function(g,f,k,n,m){var p=this.api(!0);k===q||null===k?p.row(f).data(g):p.cell(f,k).data(g);(m===q||m)&&p.columns.adjust(); -(n===q||n)&&p.draw();return 0};this.fnVersionCheck=M.fnVersionCheck;var c=this,d=b===q,e=this.length;d&&(b={});this.oApi=this.internal=M.internal;for(var h in u.ext.internal)h&&(this[h]=ec(h));this.each(function(){var g={},f=1").appendTo(t));r.nTHead=H[0];var ea=t.children("tbody");0===ea.length&&(ea=l("").insertAfter(H));r.nTBody=ea[0];H=t.children("tfoot");0===H.length&&0").appendTo(t));0===H.length||0===H.children().length?t.addClass(C.sNoFooter):0/g,sc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,tc=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,qb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,Z=function(a){return a&&!0!==a&&"-"!==a?!1:!0},gc= -function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},hc=function(a,b){rb[b]||(rb[b]=new RegExp(ib(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(rb[b],"."):a},sb=function(a,b,c){var d="string"===typeof a;if(Z(a))return!0;b&&d&&(a=hc(a,b));c&&d&&(a=a.replace(qb,""));return!isNaN(parseFloat(a))&&isFinite(a)},ic=function(a,b,c){return Z(a)?!0:Z(a)||"string"===typeof a?sb(a.replace(Ua,""),b,c)?!0:null:null},U=function(a,b,c){var d=[],e=0,h=a.length;if(c!==q)for(;e< -h;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;ea.length)){var b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d< -e;d++){if(b[d]===c){b=!1;break a}c=b[d]}}b=!0}if(b)return a.slice();b=[];e=a.length;var h,g=0;d=0;a:for(;d")[0],qc=Qa.textContent!==q,rc=/<.*?>/g,gb=u.util.throttle,lc=[],N=Array.prototype,uc=function(a){var b,c=u.settings,d=l.map(c,function(h,g){return h.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e=l.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=l(a):a instanceof l&&(b=a)}else return[];if(b)return b.map(function(h){e= -l.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var B=function(a,b){if(!(this instanceof B))return new B(a,b);var c=[],d=function(g){(g=uc(g))&&c.push.apply(c,g)};if(Array.isArray(a))for(var e=0,h=a.length;em[n])d(g.length+m[n],k);else if("string"===typeof m[n]){var p=0;for(f=g.length;pb&&a[e]--; -1!=d&&c===q&&a.splice(d,1)}function wa(a,b,c,d){var e=a.aoData[b],h,f=function(k,m){for(;k.childNodes.length;)k.removeChild(k.firstChild);k.innerHTML=T(a,b,m,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var g=e.anCells;if(g)if(d!==q)f(g[d],d);else for(c=0,h=g.length;c").appendTo(d));var k=0;for(b=g.length;k=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);g=a._iDisplayStart;var n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,V(a,!1);else if(!k)a.iDraw++;else if(!a.bDestroying&&!b){Gb(a);return}if(0!==m.length)for(b=k?a.aoData.length:n,f=k?0:g;f",{"class":h?e[0]:""}).append(l("",{valign:"top",colSpan:oa(a),"class":a.oClasses.sRowEmpty}).html(d))[0];F(a,"aoHeaderCallback","header",[l(a.nTHead).children("tr")[0],cb(a),g,n,m]);F(a,"aoFooterCallback", +"footer",[l(a.nTFoot).children("tr")[0],cb(a),g,n,m]);e=l(a.nTBody);e.children().detach();e.append(l(c));F(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ka(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Hb(a);d?za(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;ja(a);a._drawHold=!1}function Ib(a){var b=a.oClasses,c=l(a.nTable);c=l("
").insertBefore(c);var d=a.oFeatures,e=l("
",{id:a.sTableId+"_wrapper", +"class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var h=a.sDom.split(""),f,g,k,m,n,p,t=0;t")[0];m=h[t+1];if("'"==m||'"'==m){n="";for(p=2;h[t+p]!=m;)n+=h[t+p],p++;"H"==n?n=b.sJUIHeader:"F"==n&&(n=b.sJUIFooter);-1!=n.indexOf(".")?(m=n.split("."),k.id=m[0].substr(1,m[0].length-1),k.className=m[1]):"#"==n.charAt(0)?k.id=n.substr(1,n.length-1):k.className=n;t+=p}e.append(k); +e=l(k)}else if(">"==g)e=e.parent();else if("l"==g&&d.bPaginate&&d.bLengthChange)f=Jb(a);else if("f"==g&&d.bFilter)f=Kb(a);else if("r"==g&&d.bProcessing)f=Lb(a);else if("t"==g)f=Mb(a);else if("i"==g&&d.bInfo)f=Nb(a);else if("p"==g&&d.bPaginate)f=Ob(a);else if(0!==u.ext.feature.length)for(k=u.ext.feature,p=0,m=k.length;p',g=d.sSearch;g=g.match(/_INPUT_/)?g.replace("_INPUT_",f):g+f;b=l("
",{id:h.f?null:c+"_filter","class":b.sFilter}).append(l("
").addClass(b.sLength);a.aanFeatures.l||(k[0].id=c+"_length");k.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));l("select",k).val(a._iDisplayLength).on("change.DT",function(m){jb(a,l(this).val());ja(a)});l(a.nTable).on("length.dt.DT",function(m,n,p){a===n&&l("select",k).val(p)});return k[0]}function Ob(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(f){ja(f)};b=l("
").addClass(a.oClasses.sPaging+b)[0]; +var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(f){if(d){var g=f._iDisplayStart,k=f._iDisplayLength,m=f.fnRecordsDisplay(),n=-1===k;g=n?0:Math.ceil(g/k);k=n?1:Math.ceil(m/k);m=c(g,k);var p;n=0;for(p=h.p.length;nh&& +(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function V(a,b){a.oFeatures.bProcessing&&l(a.aanFeatures.r).css("display",b?"block":"none"); +F(a,null,"processing",[a,b])}function Mb(a){var b=l(a.nTable),c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,h=a.oClasses,f=b.children("caption"),g=f.length?f[0]._captionSide:null,k=l(b[0].cloneNode(!1)),m=l(b[0].cloneNode(!1)),n=b.children("tfoot");n.length||(n=null);k=l("
",{"class":h.sScrollWrapper}).append(l("
",{"class":h.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?d?K(d):null:"100%"}).append(l("
",{"class":h.sScrollHeadInner}).css({"box-sizing":"content-box", +width:c.sXInner||"100%"}).append(k.removeAttr("id").css("margin-left",0).append("top"===g?f:null).append(b.children("thead"))))).append(l("
",{"class":h.sScrollBody}).css({position:"relative",overflow:"auto",width:d?K(d):null}).append(b));n&&k.append(l("
",{"class":h.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?K(d):null:"100%"}).append(l("
",{"class":h.sScrollFootInner}).append(m.removeAttr("id").css("margin-left",0).append("bottom"===g?f:null).append(b.children("tfoot"))))); +b=k.children();var p=b[0];h=b[1];var t=n?b[2]:null;if(d)l(h).on("scroll.DT",function(v){v=this.scrollLeft;p.scrollLeft=v;n&&(t.scrollLeft=v)});l(h).css("max-height",e);c.bCollapse||l(h).css("height",e);a.nScrollHead=p;a.nScrollBody=h;a.nScrollFoot=t;a.aoDrawCallback.push({fn:Ha,sName:"scrolling"});return k[0]}function Ha(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY;b=b.iBarWidth;var h=l(a.nScrollHead),f=h[0].style,g=h.children("div"),k=g[0].style,m=g.children("table");g=a.nScrollBody;var n=l(g),p= +g.style,t=l(a.nScrollFoot).children("div"),v=t.children("table"),x=l(a.nTHead),w=l(a.nTable),r=w[0],C=r.style,G=a.nTFoot?l(a.nTFoot):null,aa=a.oBrowser,L=aa.bScrollOversize;U(a.aoColumns,"nTh");var O=[],I=[],H=[],ea=[],Y,Ca=function(D){D=D.style;D.paddingTop="0";D.paddingBottom="0";D.borderTopWidth="0";D.borderBottomWidth="0";D.height=0};var fa=g.scrollHeight>g.clientHeight;if(a.scrollBarVis!==fa&&a.scrollBarVis!==q)a.scrollBarVis=fa,ta(a);else{a.scrollBarVis=fa;w.children("thead, tfoot").remove(); +if(G){var ba=G.clone().prependTo(w);var la=G.find("tr");ba=ba.find("tr")}var mb=x.clone().prependTo(w);x=x.find("tr");fa=mb.find("tr");mb.find("th, td").removeAttr("tabindex");c||(p.width="100%",h[0].style.width="100%");l.each(Na(a,mb),function(D,W){Y=ua(a,D);W.style.width=a.aoColumns[Y].sWidth});G&&ca(function(D){D.style.width=""},ba);h=w.outerWidth();""===c?(C.width="100%",L&&(w.find("tbody").height()>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(w.outerWidth()-b)),h=w.outerWidth()): +""!==d&&(C.width=K(d),h=w.outerWidth());ca(Ca,fa);ca(function(D){var W=z.getComputedStyle?z.getComputedStyle(D).width:K(l(D).width());H.push(D.innerHTML);O.push(W)},fa);ca(function(D,W){D.style.width=O[W]},x);l(fa).height(0);G&&(ca(Ca,ba),ca(function(D){ea.push(D.innerHTML);I.push(K(l(D).css("width")))},ba),ca(function(D,W){D.style.width=I[W]},la),l(ba).height(0));ca(function(D,W){D.innerHTML='
'+H[W]+"
";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow= +"hidden";D.style.width=O[W]},fa);G&&ca(function(D,W){D.innerHTML='
'+ea[W]+"
";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=I[W]},ba);w.outerWidth()g.offsetHeight||"scroll"==n.css("overflow-y")?h+b:h,L&&(g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(la-b)),""!==c&&""===d||da(a,1,"Possible column misalignment",6)):la="100%";p.width=K(la);f.width=K(la);G&&(a.nScrollFoot.style.width= +K(la));!e&&L&&(p.height=K(r.offsetHeight+b));c=w.outerWidth();m[0].style.width=K(c);k.width=K(c);d=w.height()>g.clientHeight||"scroll"==n.css("overflow-y");e="padding"+(aa.bScrollbarLeft?"Left":"Right");k[e]=d?b+"px":"0px";G&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");w.children("colgroup").insertBefore(w.children("thead"));n.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(g.scrollTop=0)}}function ca(a,b,c){for(var d=0,e=0,h=b.length,f,g;e").appendTo(g.find("tbody"));g.find("thead, tfoot").remove();g.append(l(a.nTHead).clone()).append(l(a.nTFoot).clone());g.find("tfoot th, tfoot td").css("width","");m=Na(a,g.find("thead")[0]);for(v=0;v").css({width:w.sWidthOrig, +margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v=0;v").css(h||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(g).appendTo(p);h&&f?g.width(f):h?(g.css("width","auto"),g.removeAttr("width"),g.width()").css("width",K(a)).appendTo(b||A.body);b=a[0].offsetWidth;a.remove();return b}function $b(a,b){var c=ac(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]: +l("").html(T(a,c,b,"display"))[0]}function ac(a,b){for(var c,d=-1,e=-1,h=0,f=a.aoData.length;hd&&(d=c.length,e=h);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function pa(a){var b=[],c=a.aoColumns;var d=a.aaSortingFixed;var e=l.isPlainObject(d);var h=[];var f=function(n){n.length&&!Array.isArray(n[0])?h.push(n):l.merge(h,n)};Array.isArray(d)&&f(d); +e&&d.pre&&f(d.pre);f(a.aaSorting);e&&d.post&&f(d.post);for(a=0;aG?1:0;if(0!==C)return"asc"===r.dir?C:-C}C=c[n];G=c[p];return CG?1:0}):f.sort(function(n,p){var t,v=g.length,x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;tG?1:0})}a.bSorted=!0}function cc(a){var b=a.aoColumns,c=pa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d/g,"");var k=h.nTh;k.removeAttribute("aria-sort");h.bSortable&&(0e?e+1:3))}e=0;for(h=d.length;ee?e+1:3))}a.aLastSort=d}function bc(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,va(a,b)));for(var h,f=u.ext.type.order[c.sType+"-pre"],g=0,k=a.aoData.length;g=e.length?[0, +m[1]]:m)}));b.search!==q&&l.extend(a.oPreviousSearch,Wb(b.search));if(b.columns){f=0;for(d=b.columns.length;f=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function fb(a,b){a=a.renderer;var c=u.ext.renderer[b];return l.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function Q(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Da(a,b){var c=ec.numbers_length,d=Math.floor(c/2); +b<=c?a=ra(0,b):a<=d?(a=ra(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=ra(b-(c-2),b):(a=ra(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function Wa(a){l.each({num:function(b){return Ta(b,a)},"num-fmt":function(b){return Ta(b,a,rb)},"html-num":function(b){return Ta(b,a,Ua)},"html-num-fmt":function(b){return Ta(b,a,Ua,rb)}},function(b,c){M.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(M.type.search[b+a]=M.type.search.html)})}function fc(a){return function(){var b= +[Sa(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a,b){if(this instanceof u)return l(a).DataTable(b);b=a;this.$=function(f,g){return this.api(!0).$(f,g)};this._=function(f,g){return this.api(!0).rows(f,g).data()};this.api=function(f){return f?new B(Sa(this[M.iApiIndex])):new B(this)};this.fnAddData=function(f,g){var k=this.api(!0);f=Array.isArray(f)&&(Array.isArray(f[0])||l.isPlainObject(f[0]))?k.rows.add(f):k.row.add(f); +(g===q||g)&&k.draw();return f.flatten().toArray()};this.fnAdjustColumnSizing=function(f){var g=this.api(!0).columns.adjust(),k=g.settings()[0],m=k.oScroll;f===q||f?g.draw(!1):(""!==m.sX||""!==m.sY)&&Ha(k)};this.fnClearTable=function(f){var g=this.api(!0).clear();(f===q||f)&&g.draw()};this.fnClose=function(f){this.api(!0).row(f).child.hide()};this.fnDeleteRow=function(f,g,k){var m=this.api(!0);f=m.rows(f);var n=f.settings()[0],p=n.aoData[f[0][0]];f.remove();g&&g.call(this,n,p);(k===q||k)&&m.draw(); +return p};this.fnDestroy=function(f){this.api(!0).destroy(f)};this.fnDraw=function(f){this.api(!0).draw(f)};this.fnFilter=function(f,g,k,m,n,p){n=this.api(!0);null===g||g===q?n.search(f,k,m,p):n.column(g).search(f,k,m,p);n.draw()};this.fnGetData=function(f,g){var k=this.api(!0);if(f!==q){var m=f.nodeName?f.nodeName.toLowerCase():"";return g!==q||"td"==m||"th"==m?k.cell(f,g).data():k.row(f).data()||null}return k.data().toArray()};this.fnGetNodes=function(f){var g=this.api(!0);return f!==q?g.row(f).node(): +g.rows().nodes().flatten().toArray()};this.fnGetPosition=function(f){var g=this.api(!0),k=f.nodeName.toUpperCase();return"TR"==k?g.row(f).index():"TD"==k||"TH"==k?(f=g.cell(f).index(),[f.row,f.columnVisible,f.column]):null};this.fnIsOpen=function(f){return this.api(!0).row(f).child.isShown()};this.fnOpen=function(f,g,k){return this.api(!0).row(f).child(g,k).show().child()[0]};this.fnPageChange=function(f,g){f=this.api(!0).page(f);(g===q||g)&&f.draw(!1)};this.fnSetColumnVis=function(f,g,k){f=this.api(!0).column(f).visible(g); +(k===q||k)&&f.columns.adjust().draw()};this.fnSettings=function(){return Sa(this[M.iApiIndex])};this.fnSort=function(f){this.api(!0).order(f).draw()};this.fnSortListener=function(f,g,k){this.api(!0).order.listener(f,g,k)};this.fnUpdate=function(f,g,k,m,n){var p=this.api(!0);k===q||null===k?p.row(g).data(f):p.cell(g,k).data(f);(n===q||n)&&p.columns.adjust();(m===q||m)&&p.draw();return 0};this.fnVersionCheck=M.fnVersionCheck;var c=this,d=b===q,e=this.length;d&&(b={});this.oApi=this.internal=M.internal; +for(var h in u.ext.internal)h&&(this[h]=fc(h));this.each(function(){var f={},g=1").appendTo(t));r.nTHead=H[0];var ea=t.children("tbody");0===ea.length&&(ea=l("").insertAfter(H)); +r.nTBody=ea[0];H=t.children("tfoot");0===H.length&&0").appendTo(t));0===H.length||0===H.children().length?t.addClass(C.sNoFooter):0/g,uc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,vc=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,rb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,Z=function(a){return a&&!0!==a&&"-"!==a?!1:!0},hc=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},ic=function(a,b){sb[b]||(sb[b]=new RegExp(ib(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g, +"").replace(sb[b],"."):a},tb=function(a,b,c){var d="string"===typeof a;if(Z(a))return!0;b&&d&&(a=ic(a,b));c&&d&&(a=a.replace(rb,""));return!isNaN(parseFloat(a))&&isFinite(a)},jc=function(a,b,c){return Z(a)?!0:Z(a)||"string"===typeof a?tb(a.replace(Ua,""),b,c)?!0:null:null},U=function(a,b,c){var d=[],e=0,h=a.length;if(c!==q)for(;ea.length)){var b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d")[0],sc=Qa.textContent!==q,tc=/<.*?>/g,gb=u.util.throttle,nc=[],N=Array.prototype,wc=function(a){var b,c=u.settings,d=l.map(c,function(h,f){return h.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e=l.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=l(a):a instanceof l&&(b=a)}else return[];if(b)return b.map(function(h){e= +l.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var B=function(a,b){if(!(this instanceof B))return new B(a,b);var c=[],d=function(f){(f=wc(f))&&c.push.apply(c,f)};if(Array.isArray(a))for(var e=0,h=a.length;ea?new B(b[a],this[a]):null},filter:function(a){var b=[];if(N.filter)b=N.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(f),l("td",k).addClass(f).html(g)[0].colSpan= -oa(a),e.push(k[0]))};h(c,d);b._details&&b._details.detach();b._details=l(e);b._detailsShow&&b._details.insertAfter(b.nTr)},wb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details=q,l(a.nTr).removeClass("dt-hasChild"),qa(c[0]))},oc=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];d._details&&((d._detailsShow=b)?(d._details.insertAfter(d.nTr),l(d.nTr).addClass("dt-hasChild")):(d._details.detach(), -l(d.nTr).removeClass("dt-hasChild")),F(c[0],null,"childRow",[b,a.row(a[0])]),xc(c[0]),qa(c[0]))}},xc=function(a){var b=new B(a),c=a.aoData;b.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0f){var m=l.map(d,function(p,t){return p.bVisible?t:null});return[m[m.length+f]]}return[ua(a,f)];case "name":return l.map(e,function(p,t){return p===n[1]?t:null});default:return[]}if(g.nodeName&&g._DT_CellIndex)return[g._DT_CellIndex.column]; -f=l(h).filter(g).map(function(){return l.inArray(this,h)}).toArray();if(f.length||!g.nodeName)return f;f=l(g).closest("*[data-dt-column]");return f.length?[f.data("dt-column")]:[]},a,c)};y("columns()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=ub(b);var c=this.iterator("table",function(d){return zc(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh}, -1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",pc,1)});J("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,h){return Ea(b.aoData,h,"search"=== -a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return Ea(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(e,h){if(a===q)return e.aoColumns[h].bVisible;var g=e.aoColumns,f=g[h],k=e.aoData,n;if(a!==q&&f.bVisible!==a){if(a){var m=l.inArray(!0,U(g,"bVisible"),h+1);g=0;for(n=k.length;g").addClass(g),l("td",k).addClass(g).html(f)[0].colSpan= +oa(a),e.push(k[0]))};h(c,d);b._details&&b._details.detach();b._details=l(e);b._detailsShow&&b._details.insertAfter(b.nTr)},xb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details=q,l(a.nTr).removeClass("dt-hasChild"),qa(c[0]))},qc=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];d._details&&((d._detailsShow=b)?(d._details.insertAfter(d.nTr),l(d.nTr).addClass("dt-hasChild")):(d._details.detach(), +l(d.nTr).removeClass("dt-hasChild")),F(c[0],null,"childRow",[b,a.row(a[0])]),zc(c[0]),qa(c[0]))}},zc=function(a){var b=new B(a),c=a.aoData;b.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0g){var n=l.map(d,function(p,t){return p.bVisible?t:null});return[n[n.length+g]]}return[ua(a,g)];case "name":return l.map(e,function(p,t){return p===m[1]?t:null});default:return[]}if(f.nodeName&&f._DT_CellIndex)return[f._DT_CellIndex.column]; +g=l(h).filter(f).map(function(){return l.inArray(this,h)}).toArray();if(g.length||!f.nodeName)return g;g=l(f).closest("*[data-dt-column]");return g.length?[g.data("dt-column")]:[]},a,c)};y("columns()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=vb(b);var c=this.iterator("table",function(d){return Bc(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh}, +1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",rc,1)});J("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,h){return Ea(b.aoData,h,"search"=== +a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return Ea(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(e,h){if(a===q)return e.aoColumns[h].bVisible;var f=e.aoColumns,g=f[h],k=e.aoData,m;if(a!==q&&g.bVisible!==a){if(a){var n=l.inArray(!0,U(f,"bVisible"),h+1);f=0;for(m=k.length;fd;return!0};u.isDataTable=u.fnIsDataTable=function(a){var b= l(a).get(0),c=!1;if(a instanceof u.Api)return!0;l.each(u.settings,function(d,e){d=e.nScrollHead?l("table",e.nScrollHead)[0]:null;var h=e.nScrollFoot?l("table",e.nScrollFoot)[0]:null;if(e.nTable===b||d===b||h===b)c=!0});return c};u.tables=u.fnTables=function(a){var b=!1;l.isPlainObject(a)&&(b=a.api,a=a.visible);var c=l.map(u.settings,function(d){if(!a||a&&l(d.nTable).is(":visible"))return d.nTable});return b?new B(c):c};u.camelToHungarian=P;y("$()",function(a,b){b=this.rows(b).nodes();b=l(b);return l([].concat(b.filter(a).toArray(), b.find(a).toArray()))});l.each(["on","one","off"],function(a,b){y(b+"()",function(){var c=Array.prototype.slice.call(arguments);c[0]=l.map(c[0].split(/\s/),function(e){return e.match(/\.dt\b/)?e:e+".dt"}).join(" ");var d=l(this.tables().nodes());d[b].apply(d,c);return this})});y("clear()",function(){return this.iterator("table",function(a){Ka(a)})});y("settings()",function(){return new B(this.context,this.context)});y("init()",function(){var a=this.context;return a.length?a[0].oInit:null});y("data()", -function(){return this.iterator("table",function(a){return U(a.aoData,"_aData")}).flatten()});y("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,h=b.nTBody,g=b.nTHead,f=b.nTFoot,k=l(e);h=l(h);var n=l(b.nTableWrapper),m=l.map(b.aoData,function(t){return t.nTr}),p;b.bDestroying=!0;F(b,"aoDestroyCallback","destroy",[b]);a||(new B(b)).columns().visible(!0);n.off(".DT").find(":not(tbody *)").off(".DT");l(z).off(".DT-"+b.sInstance); -e!=g.parentNode&&(k.children("thead").detach(),k.append(g));f&&e!=f.parentNode&&(k.children("tfoot").detach(),k.append(f));b.aaSorting=[];b.aaSortingFixed=[];Ra(b);l(m).removeClass(b.asStripeClasses.join(" "));l("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);h.children().detach();h.append(m);g=a?"remove":"detach";k[g]();n[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),k.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&& -h.children().each(function(t){l(this).addClass(b.asDestroyStripes[t%p])}));c=l.inArray(b,u.settings);-1!==c&&u.settings.splice(c,1)})});l.each(["column","row","cell"],function(a,b){y(b+"s().every()",function(c){var d=this.selector.opts,e=this;return this.iterator(b,function(h,g,f,k,n){c.call(e[b](g,"cell"===b?f:d,"cell"===b?d:q),g,f,k,n)})})});y("i18n()",function(a,b,c){var d=this.context[0];a=na(a)(d.oLanguage);a===q&&(a=b);c!==q&&l.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)}); -u.version="1.11.0";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,"return":!1};u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};u.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null, +function(){return this.iterator("table",function(a){return U(a.aoData,"_aData")}).flatten()});y("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,h=b.nTBody,f=b.nTHead,g=b.nTFoot,k=l(e);h=l(h);var m=l(b.nTableWrapper),n=l.map(b.aoData,function(t){return t.nTr}),p;b.bDestroying=!0;F(b,"aoDestroyCallback","destroy",[b]);a||(new B(b)).columns().visible(!0);m.off(".DT").find(":not(tbody *)").off(".DT");l(z).off(".DT-"+b.sInstance); +e!=f.parentNode&&(k.children("thead").detach(),k.append(f));g&&e!=g.parentNode&&(k.children("tfoot").detach(),k.append(g));b.aaSorting=[];b.aaSortingFixed=[];Ra(b);l(n).removeClass(b.asStripeClasses.join(" "));l("th, td",f).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);h.children().detach();h.append(n);f=a?"remove":"detach";k[f]();m[f]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),k.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&& +h.children().each(function(t){l(this).addClass(b.asDestroyStripes[t%p])}));c=l.inArray(b,u.settings);-1!==c&&u.settings.splice(c,1)})});l.each(["column","row","cell"],function(a,b){y(b+"s().every()",function(c){var d=this.selector.opts,e=this;return this.iterator(b,function(h,f,g,k,m){c.call(e[b](f,"cell"===b?g:d,"cell"===b?d:q),f,g,k,m)})})});y("i18n()",function(a,b,c){var d=this.context[0];a=na(a)(d.oLanguage);a===q&&(a=b);c!==q&&l.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)}); +u.version="1.11.3";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,"return":!1};u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};u.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null, sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};u.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1, bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}}, fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last", @@ -179,23 +180,23 @@ sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers" {oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{}, aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0, aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:q,oAjaxData:q,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==Q(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length}, -fnRecordsDisplay:function(){return"ssp"==Q(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,h=e.bPaginate;return e.bServerSide?!1===h||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!h||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=M={buttons:{},classes:{},build:"dt/dt-1.11.0", +fnRecordsDisplay:function(){return"ssp"==Q(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,h=e.bPaginate;return e.bServerSide?!1===h||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!h||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=M={buttons:{},classes:{},build:"dt/dt-1.11.3/cr-1.5.5/fh-3.2.1/r-2.2.9", errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:u.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:u.version};l.extend(M,{afnFiltering:M.search,aTypes:M.type.detect,ofnSearch:M.type.search,oSort:M.type.order,afnSortData:M.order,aoFeatures:M.feature,oApi:M.internal,oStdClasses:M.classes,oPagination:M.pager});l.extend(u.ext.classes, {sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled", -sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var dc= -u.ext.pager;l.extend(dc,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Da(a,b)]},simple_numbers:function(a,b){return["previous",Da(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Da(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Da(a,b),"last"]},_numbers:Da,numbers_length:7});l.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,h){var g=a.oClasses,f=a.oLanguage.oPaginate, -k=a.oLanguage.oAria.paginate||{},n,m,p=0,t=function(x,w){var r,C=g.sPageButtonDisabled,G=function(I){lb(a,I.data.action,!0)};var aa=0;for(r=w.length;aa").appendTo(x);t(O,L)}else{n=null;m=L;O=a.iTabIndex;switch(L){case "ellipsis":x.append('');break;case "first":n=f.sFirst;0===e&&(O=-1,m+=" "+C);break;case "previous":n=f.sPrevious;0===e&&(O=-1,m+=" "+C);break;case "next":n=f.sNext;if(0=== -h||e===h-1)O=-1,m+=" "+C;break;case "last":n=f.sLast;if(0===h||e===h-1)O=-1,m+=" "+C;break;default:n=a.fnFormatNumber(L+1),m=e===L?g.sPageButtonActive:""}null!==n&&(O=l("",{"class":g.sPageButton+" "+m,"aria-controls":a.sTableId,"aria-label":k[L],"data-dt-idx":p,tabindex:O,id:0===c&&"string"===typeof L?a.sTableId+"_"+L:null}).html(n).appendTo(x),ob(O,{action:L},G),p++)}}};try{var v=l(b).find(A.activeElement).data("dt-idx")}catch(x){}t(l(b).empty(),d);v!==q&&l(b).find("[data-dt-idx="+v+"]").trigger("focus")}}}); -l.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return sb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!sc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||Z(a)?"date":null},function(a,b){b=b.oLanguage.sDecimal;return sb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return ic(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return ic(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return Z(a)||"string"===typeof a&&-1!==a.indexOf("<")? -"html":null}]);l.extend(u.ext.type.search,{html:function(a){return Z(a)?a:"string"===typeof a?a.replace(fc," ").replace(Ua,""):""},string:function(a){return Z(a)?a:"string"===typeof a?a.replace(fc," "):a}});var Ta=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=hc(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};l.extend(M.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return Z(a)?"":a.replace?a.replace(/<.*?>/g, -"").toLowerCase():a+""},"string-pre":function(a){return Z(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return ab?-1:0}});Wa("");l.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){l(a.nTable).on("order.dt.DT",function(e,h,g,f){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==f[e]?d.sSortAsc:"desc"==f[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c, -d){l("
").addClass(d.sSortJUIWrapper).append(b.contents()).append(l("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);l(a.nTable).on("order.dt.DT",function(e,h,g,f){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==f[e]?d.sSortAsc:"desc"==f[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==f[e]?d.sSortJUIAsc:"desc"== -f[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var xb=function(a){return"string"===typeof a?a.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):a};u.render={number:function(a,b,c,d,e){return{display:function(h){if("number"!==typeof h&&"string"!==typeof h)return h;var g=0>h?"-":"",f=parseFloat(h);if(isNaN(f))return xb(h);f=f.toFixed(c);h=Math.abs(f);f=parseInt(h,10);h=c?b+(h-f).toFixed(c).substring(2):"";0===f&&0===parseFloat(h)&&(g="");return g+(d||"")+f.toString().replace(/\B(?=(\d{3})+(?!\d))/g, -a)+h+(e||"")}}},text:function(){return{display:xb,filter:xb}}};l.extend(u.ext.internal,{_fnExternApiFunc:ec,_fnBuildAjax:Oa,_fnAjaxUpdate:Fb,_fnAjaxParameters:Ob,_fnAjaxUpdateDraw:Pb,_fnAjaxDataSrc:Aa,_fnAddColumn:Xa,_fnColumnOptions:Ga,_fnAdjustColumnSizing:ta,_fnVisibleToColumnIndex:ua,_fnColumnIndexToVisible:va,_fnVisbleColumns:oa,_fnGetColumns:Ia,_fnColumnTypes:Za,_fnApplyColumnDefs:Cb,_fnHungarianMap:E,_fnCamelToHungarian:P,_fnLanguageCompat:ma,_fnBrowserDetect:Ab,_fnAddData:ia,_fnAddTr:Ja,_fnNodeToDataIndex:function(a, -b){return b._DT_RowIndex!==q?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return l.inArray(c,a.aoData[b].anCells)},_fnGetCellData:T,_fnSetCellData:Db,_fnSplitObjNotation:bb,_fnGetObjectDataFn:na,_fnSetObjectDataFn:ha,_fnGetDataMaster:cb,_fnClearTable:Ka,_fnDeleteIndex:La,_fnInvalidate:wa,_fnGetRowElements:ab,_fnCreateTr:$a,_fnBuildHead:Eb,_fnDrawHead:ya,_fnDraw:ja,_fnReDraw:ka,_fnAddOptionsHtml:Hb,_fnDetectHeader:xa,_fnGetUniqueThs:Na,_fnFeatureHtmlFilter:Jb,_fnFilterComplete:za,_fnFilterCustom:Sb, -_fnFilterColumn:Rb,_fnFilter:Qb,_fnFilterCreateSearch:hb,_fnEscapeRegex:ib,_fnFilterData:Tb,_fnFeatureHtmlInfo:Mb,_fnUpdateInfo:Wb,_fnInfoMacros:Xb,_fnInitialise:Ba,_fnInitComplete:Pa,_fnLengthChange:jb,_fnFeatureHtmlLength:Ib,_fnFeatureHtmlPaginate:Nb,_fnPageChange:lb,_fnFeatureHtmlProcessing:Kb,_fnProcessingDisplay:V,_fnFeatureHtmlTable:Lb,_fnScrollDraw:Ha,_fnApplyToChildren:ca,_fnCalculateColumnWidths:Ya,_fnThrottle:gb,_fnConvertToWidth:Yb,_fnGetWidestNode:Zb,_fnGetMaxLenString:$b,_fnStringToCss:K, -_fnSortFlatten:pa,_fnSort:Gb,_fnSortAria:bc,_fnSortListener:nb,_fnSortAttachListener:eb,_fnSortingClasses:Ra,_fnSortData:ac,_fnSaveState:qa,_fnLoadState:cc,_fnSettingsFromNode:Sa,_fnLog:da,_fnMap:X,_fnBindAction:ob,_fnCallbackReg:R,_fnCallbackFire:F,_fnLengthOverflow:kb,_fnRenderer:fb,_fnDataSource:Q,_fnRowAttributes:db,_fnExtend:pb,_fnCalculateEnd:function(){}});l.fn.dataTable=u;u.$=l;l.fn.dataTableSettings=u.settings;l.fn.dataTableExt=u.ext;l.fn.DataTable=function(a){return l(this).dataTable(a).api()}; -l.each(u,function(a,b){l.fn.DataTable[a]=b});return u}); +sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ec= +u.ext.pager;l.extend(ec,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Da(a,b)]},simple_numbers:function(a,b){return["previous",Da(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Da(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Da(a,b),"last"]},_numbers:Da,numbers_length:7});l.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,h){var f=a.oClasses,g=a.oLanguage.oPaginate, +k=a.oLanguage.oAria.paginate||{},m,n,p=0,t=function(x,w){var r,C=f.sPageButtonDisabled,G=function(I){lb(a,I.data.action,!0)};var aa=0;for(r=w.length;aa").appendTo(x);t(O,L)}else{m=null;n=L;O=a.iTabIndex;switch(L){case "ellipsis":x.append('');break;case "first":m=g.sFirst;0===e&&(O=-1,n+=" "+C);break;case "previous":m=g.sPrevious;0===e&&(O=-1,n+=" "+C);break;case "next":m=g.sNext;if(0=== +h||e===h-1)O=-1,n+=" "+C;break;case "last":m=g.sLast;if(0===h||e===h-1)O=-1,n+=" "+C;break;default:m=a.fnFormatNumber(L+1),n=e===L?f.sPageButtonActive:""}null!==m&&(O=l("",{"class":f.sPageButton+" "+n,"aria-controls":a.sTableId,"aria-label":k[L],"data-dt-idx":p,tabindex:O,id:0===c&&"string"===typeof L?a.sTableId+"_"+L:null}).html(m).appendTo(x),ob(O,{action:L},G),p++)}}};try{var v=l(b).find(A.activeElement).data("dt-idx")}catch(x){}t(l(b).empty(),d);v!==q&&l(b).find("[data-dt-idx="+v+"]").trigger("focus")}}}); +l.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return tb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!uc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||Z(a)?"date":null},function(a,b){b=b.oLanguage.sDecimal;return tb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return Z(a)||"string"===typeof a&&-1!==a.indexOf("<")? +"html":null}]);l.extend(u.ext.type.search,{html:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," ").replace(Ua,""):""},string:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," "):a}});var Ta=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=ic(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};l.extend(M.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return Z(a)?"":a.replace?a.replace(/<.*?>/g, +"").toLowerCase():a+""},"string-pre":function(a){return Z(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return ab?-1:0}});Wa("");l.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c, +d){l("
").addClass(d.sSortJUIWrapper).append(b.contents()).append(l("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==g[e]?d.sSortJUIAsc:"desc"== +g[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var yb=function(a){Array.isArray(a)&&(a=a.join(","));return"string"===typeof a?a.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):a};u.render={number:function(a,b,c,d,e){return{display:function(h){if("number"!==typeof h&&"string"!==typeof h)return h;var f=0>h?"-":"",g=parseFloat(h);if(isNaN(g))return yb(h);g=g.toFixed(c);h=Math.abs(g);g=parseInt(h,10);h=c?b+(h-g).toFixed(c).substring(2):"";0===g&&0===parseFloat(h)&& +(f="");return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+h+(e||"")}}},text:function(){return{display:yb,filter:yb}}};l.extend(u.ext.internal,{_fnExternApiFunc:fc,_fnBuildAjax:Oa,_fnAjaxUpdate:Gb,_fnAjaxParameters:Pb,_fnAjaxUpdateDraw:Qb,_fnAjaxDataSrc:Aa,_fnAddColumn:Xa,_fnColumnOptions:Ga,_fnAdjustColumnSizing:ta,_fnVisibleToColumnIndex:ua,_fnColumnIndexToVisible:va,_fnVisbleColumns:oa,_fnGetColumns:Ia,_fnColumnTypes:Za,_fnApplyColumnDefs:Db,_fnHungarianMap:E,_fnCamelToHungarian:P, +_fnLanguageCompat:ma,_fnBrowserDetect:Bb,_fnAddData:ia,_fnAddTr:Ja,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==q?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return l.inArray(c,a.aoData[b].anCells)},_fnGetCellData:T,_fnSetCellData:Eb,_fnSplitObjNotation:bb,_fnGetObjectDataFn:na,_fnSetObjectDataFn:ha,_fnGetDataMaster:cb,_fnClearTable:Ka,_fnDeleteIndex:La,_fnInvalidate:wa,_fnGetRowElements:ab,_fnCreateTr:$a,_fnBuildHead:Fb,_fnDrawHead:ya,_fnDraw:ja,_fnReDraw:ka,_fnAddOptionsHtml:Ib, +_fnDetectHeader:xa,_fnGetUniqueThs:Na,_fnFeatureHtmlFilter:Kb,_fnFilterComplete:za,_fnFilterCustom:Tb,_fnFilterColumn:Sb,_fnFilter:Rb,_fnFilterCreateSearch:hb,_fnEscapeRegex:ib,_fnFilterData:Ub,_fnFeatureHtmlInfo:Nb,_fnUpdateInfo:Xb,_fnInfoMacros:Yb,_fnInitialise:Ba,_fnInitComplete:Pa,_fnLengthChange:jb,_fnFeatureHtmlLength:Jb,_fnFeatureHtmlPaginate:Ob,_fnPageChange:lb,_fnFeatureHtmlProcessing:Lb,_fnProcessingDisplay:V,_fnFeatureHtmlTable:Mb,_fnScrollDraw:Ha,_fnApplyToChildren:ca,_fnCalculateColumnWidths:Ya, +_fnThrottle:gb,_fnConvertToWidth:Zb,_fnGetWidestNode:$b,_fnGetMaxLenString:ac,_fnStringToCss:K,_fnSortFlatten:pa,_fnSort:Hb,_fnSortAria:cc,_fnSortListener:nb,_fnSortAttachListener:eb,_fnSortingClasses:Ra,_fnSortData:bc,_fnSaveState:qa,_fnLoadState:dc,_fnImplementState:pb,_fnSettingsFromNode:Sa,_fnLog:da,_fnMap:X,_fnBindAction:ob,_fnCallbackReg:R,_fnCallbackFire:F,_fnLengthOverflow:kb,_fnRenderer:fb,_fnDataSource:Q,_fnRowAttributes:db,_fnExtend:qb,_fnCalculateEnd:function(){}});l.fn.dataTable=u;u.$= +l;l.fn.dataTableSettings=u.settings;l.fn.dataTableExt=u.ext;l.fn.DataTable=function(a){return l(this).dataTable(a).api()};l.each(u,function(a,b){l.fn.DataTable[a]=b});return u}); /*! @@ -205,3 +206,178 @@ l.each(u,function(a,b){l.fn.DataTable[a]=b});return u}); (function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);b&&b.fn.dataTable||(b=require("datatables.net")(a,b).$);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c,a,b,d){return c.fn.dataTable}); +/*! + Copyright 2010-2021 SpryMedia Ltd. + + This source file is free software, available under the following license: + MIT license - http://datatables.net/license/mit + + This source file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + + For details please refer to: http://www.datatables.net + ColReorder 1.5.5 + ©2010-2021 SpryMedia Ltd - datatables.net/license +*/ +(function(e){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(u){return e(u,window,document)}):"object"===typeof exports?module.exports=function(u,t){u||(u=window);t&&t.fn.dataTable||(t=require("datatables.net")(u,t).$);return e(t,u,u.document)}:e(jQuery,window,document)})(function(e,u,t,z){function y(a){for(var b=[],c=0,f=a.length;cb||b>=k)this.oApi._fnLog(a,1,"ColReorder 'from' index is out of bounds: "+b);else if(0>c||c>=k)this.oApi._fnLog(a,1,"ColReorder 'to' index is out of bounds: "+ +c);else{var m=[];var d=0;for(g=k;dthis.s.fixed-1&&fMath.pow(Math.pow(this._fnCursorPosition(a,"pageX")-this.s.mouse.startX,2)+Math.pow(this._fnCursorPosition(a,"pageY")-this.s.mouse.startY,2),.5))return;this._fnCreateDragNode()}this.dom.drag.css({left:this._fnCursorPosition(a,"pageX")-this.s.mouse.offsetX,top:this._fnCursorPosition(a,"pageY")-this.s.mouse.offsetY});var c=this.s.mouse.toIndex;a=this._fnCursorPosition(a,"pageX");for(var f=function(d){for(;0<= +d;){d--;if(0>=d)return null;if(b.s.aoTargets[d+1].x!==b.s.aoTargets[d].x)return b.s.aoTargets[d]}},h=function(){for(var d=0;dp){m= +k;break}}m?(this.dom.pointer.css("left",m.x),this.s.mouse.toIndex=m.to):(this.dom.pointer.css("left",g().x),this.s.mouse.toIndex=g().to);this.s.init.bRealtime&&c!==this.s.mouse.toIndex&&(this.s.dt.oInstance.fnColReorder(this.s.mouse.fromIndex,this.s.mouse.toIndex),this.s.mouse.fromIndex=this.s.mouse.toIndex,""===this.s.dt.oScroll.sX&&""===this.s.dt.oScroll.sY||this.s.dt.oInstance.fnAdjustColumnSizing(!1),this._fnRegions())},_fnMouseUp:function(a){e(t).off(".ColReorder");null!==this.dom.drag&&(this.dom.drag.remove(), +this.dom.pointer.remove(),this.dom.drag=null,this.dom.pointer=null,this.s.dt.oInstance.fnColReorder(this.s.mouse.fromIndex,this.s.mouse.toIndex,!0),this._fnSetColumnIndexes(),""===this.s.dt.oScroll.sX&&""===this.s.dt.oScroll.sY||this.s.dt.oInstance.fnAdjustColumnSizing(!1),this.s.dt.oInstance.oApi._fnSaveState(this.s.dt),null!==this.s.reorderCallback&&this.s.reorderCallback.call(this))},_fnRegions:function(){var a=this.s.dt.aoColumns,b=this._fnIsLtr();this.s.aoTargets.splice(0,this.s.aoTargets.length); +var c=e(this.s.dt.nTable).offset().left,f=[];e.each(a,function(l,k){if(k.bVisible&&"none"!==k.nTh.style.display){k=e(k.nTh);var p=k.offset().left;b&&(p+=k.outerWidth());f.push({index:l,bound:p});c=p}else f.push({index:l,bound:c})});var h=f[0];a=e(a[h.index].nTh).outerWidth();this.s.aoTargets.push({to:0,x:h.bound-a});for(h=0;h
").addClass("DTCR_pointer").css({position:"absolute",top:a?e(e(this.s.dt.nScrollBody).parent()).offset().top:e(this.s.dt.nTable).offset().top,height:a?e(e(this.s.dt.nScrollBody).parent()).height():e(this.s.dt.nTable).height()}).appendTo("body")},_fnSetColumnIndexes:function(){e.each(this.s.dt.aoColumns,function(a,b){e(b.nTh).attr("data-column-index",a)})},_fnCursorPosition:function(a,b){return-1!==a.type.indexOf("touch")?a.originalEvent.touches[0][b]:a[b]},_fnIsLtr:function(){return"rtl"!== +e(this.s.dt.nTable).css("direction")}});r.defaults={aiOrder:null,bEnable:!0,bRealtime:!0,iFixedColumnsLeft:0,iFixedColumnsRight:0,fnReorderCallback:null};r.version="1.5.5";e.fn.dataTable.ColReorder=r;e.fn.DataTable.ColReorder=r;"function"==typeof e.fn.dataTable&&"function"==typeof e.fn.dataTableExt.fnVersionCheck&&e.fn.dataTableExt.fnVersionCheck("1.10.8")?e.fn.dataTableExt.aoFeatures.push({fnInit:function(a){var b=a.oInstance;a._colReorder?b.oApi._fnLog(a,1,"ColReorder attempted to initialise twice. Ignoring second"): +(b=a.oInit,new r(a,b.colReorder||b.oColReorder||{}));return null},cFeature:"R",sFeature:"ColReorder"}):alert("Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download");e(t).on("preInit.dt.colReorder",function(a,b){if("dt"===a.namespace){a=b.oInit.colReorder;var c=D.defaults.colReorder;if(a||c)c=e.extend({},a,c),!1!==a&&new r(b,c)}});e.fn.dataTable.Api.register("colReorder.reset()",function(){return this.iterator("table",function(a){a._colReorder.fnReset()})});e.fn.dataTable.Api.register("colReorder.order()", +function(a,b){return a?this.iterator("table",function(c){c._colReorder.fnOrder(a,b)}):this.context.length?this.context[0]._colReorder.fnOrder():null});e.fn.dataTable.Api.register("colReorder.transpose()",function(a,b){return this.context.length&&this.context[0]._colReorder?this.context[0]._colReorder.fnTranspose(a,b):a});e.fn.dataTable.Api.register("colReorder.move()",function(a,b,c,f){this.context.length&&(this.context[0]._colReorder.s.dt.oInstance.fnColReorder(a,b,c,f),this.context[0]._colReorder._fnSetColumnIndexes()); +return this});e.fn.dataTable.Api.register("colReorder.enable()",function(a){return this.iterator("table",function(b){b._colReorder&&b._colReorder.fnEnable(a)})});e.fn.dataTable.Api.register("colReorder.disable()",function(){return this.iterator("table",function(a){a._colReorder&&a._colReorder.fnDisable()})});return r}); + + +/*! + Copyright 2009-2021 SpryMedia Ltd. + + This source file is free software, available under the following license: + MIT license - http://datatables.net/license/mit + + This source file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + + For details please refer to: http://www.datatables.net + FixedHeader 3.2.1 + ©2009-2021 SpryMedia Ltd - datatables.net/license +*/ +var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(b,g,h){b instanceof String&&(b=String(b));for(var l=b.length,k=0;k'), +placeholder:null},footer:{host:null,floating:null,floatingParent:b('
'),placeholder:null}};this.dom.header.host=this.dom.thead.parent();this.dom.footer.host=this.dom.tfoot.parent();a=a.settings()[0];if(a._fixedHeader)throw"FixedHeader already initialised on table "+a.nTable.id;a._fixedHeader=this;this._constructor()};b.extend(t.prototype,{destroy:function(){this.s.dt.off(".dtfc");b(g).off(this.s.namespace);this.c.header&&this._modeChange("in-place","header",!0);this.c.footer&& +this.dom.tfoot.length&&this._modeChange("in-place","footer",!0)},enable:function(a,c){this.s.enable=a;if(c||c===l)this._positions(),this._scroll(!0)},enabled:function(){return this.s.enable},headerOffset:function(a){a!==l&&(this.c.headerOffset=a,this.update());return this.c.headerOffset},footerOffset:function(a){a!==l&&(this.c.footerOffset=a,this.update());return this.c.footerOffset},update:function(a){var c=this.s.dt.table().node();b(c).is(":visible")?this.enable(!0,!1):this.enable(!1,!1);0!==b(c).children("thead").length&& +(this._positions(),this._scroll(a!==l?a:!0))},_constructor:function(){var a=this,c=this.s.dt;b(g).on("scroll"+this.s.namespace,function(){a._scroll()}).on("resize"+this.s.namespace,k.util.throttle(function(){a.s.position.windowHeight=b(g).height();a.update()},50));var d=b(".fh-fixedHeader");!this.c.headerOffset&&d.length&&(this.c.headerOffset=d.outerHeight());d=b(".fh-fixedFooter");!this.c.footerOffset&&d.length&&(this.c.footerOffset=d.outerHeight());c.on("column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc", +function(f,e){a.update()}).on("draw.dt.dtfc",function(f,e){a.update(e===c.settings()[0]?!1:!0)});c.on("destroy.dtfc",function(){a.destroy()});this._positions();this._scroll()},_clone:function(a,c){var d=this,f=this.s.dt,e=this.dom[a],p="header"===a?this.dom.thead:this.dom.tfoot;if("footer"!==a||!this._scrollEnabled())if(!c&&e.floating)e.floating.removeClass("fixedHeader-floating fixedHeader-locked");else{e.floating&&(null!==e.placeholder&&e.placeholder.remove(),this._unsize(a),e.floating.children().detach(), +e.floating.remove());c=b(f.table().node());var n=b(c.parent()),q=this._scrollEnabled();e.floating=b(f.table().node().cloneNode(!1)).attr("aria-hidden","true").css({"table-layout":"fixed",top:0,left:0}).removeAttr("id").append(p);e.floatingParent.css({width:n.width(),overflow:"hidden",height:"fit-content",position:"fixed",left:q?c.offset().left+n.scrollLeft():0}).css("header"===a?{top:this.c.headerOffset,bottom:""}:{top:"",bottom:this.c.footerOffset}).addClass("footer"===a?"dtfh-floatingparentfoot": +"dtfh-floatingparenthead").append(e.floating).appendTo("body");this._stickyPosition(e.floating,"-");a=function(){var r=n.scrollLeft();d.s.scrollLeft={footer:r,header:r};e.floatingParent.scrollLeft(d.s.scrollLeft.header)};a();n.scroll(a);e.placeholder=p.clone(!1);e.placeholder.find("*[id]").removeAttr("id");e.host.prepend(e.placeholder);this._matchWidths(e.placeholder,e.floating)}},_stickyPosition:function(a,c){if(this._scrollEnabled()){var d=this,f="rtl"===b(d.s.dt.table().node()).css("direction"); +a.find("th").each(function(){if("sticky"===b(this).css("position")){var e=b(this).css("right"),p=b(this).css("left");"auto"===e||f?"auto"!==p&&f&&(e=+p.replace(/px/g,"")+("-"===c?-1:1)*d.s.dt.settings()[0].oBrowser.barWidth,b(this).css("left",0y?e.tfootHeight:z+e.tfootHeight-y:d+ +this.c.headerOffset+e.theadHeight-m;m="header"===c?"top":"bottom";d=this.c[c+"Offset"]-(0y&&n+this.c.headerOffsete||this.dom.header.floatingParent===l?a=!0:this.dom.header.floatingParent.css({top:this.c.headerOffset, +position:"fixed"}).append(this.dom.header.floating)):q="below":q="in-place",(a||q!==this.s.headerMode)&&this._modeChange(q,"header",a),this._horizontal("header",p));var w={offset:{top:0,left:0},height:0},x={offset:{top:0,left:0},height:0};this.c.footer&&this.dom.tfoot.length&&(this.s.enable?!m.visible||m.tfootBottom+this.c.footerOffset<=r?m="in-place":e+m.tfootHeight+this.c.footerOffset>r&&y+this.c.footerOffsetn&&(c=n-f.top,r=r+(c>-w.height?c:0)-(w.offset.top+(c<-w.height?w.height:0)+x.height),0>r&&(r=0),d.outerHeight(r),Math.round(d.outerHeight())>=Math.round(r)?b(this.dom.tfoot.parent()).addClass("fixedHeader-floating"):b(this.dom.tfoot.parent()).removeClass("fixedHeader-floating"))); +this.dom.header.floating&&this.dom.header.floatingParent.css("left",z-p);this.dom.footer.floating&&this.dom.footer.floatingParent.css("left",z-p);this.s.dt.settings()[0]._fixedColumns!==l&&(d=function(A,C,u){u===l&&(u=b("div.dtfc-"+A+"-"+C+"-blocker"),u=0===u.length?null:u.clone().appendTo("body").css("z-index",1));null!==u&&u.css({top:"top"===C?w.offset.top:x.offset.top,left:"right"===A?z+B-u.width():z});return u},this.dom.header.rightBlocker=d("right","top",this.dom.header.rightBlocker),this.dom.header.leftBlocker= +d("left","top",this.dom.header.leftBlocker),this.dom.footer.rightBlocker=d("right","bottom",this.dom.footer.rightBlocker),this.dom.footer.leftBlocker=d("left","bottom",this.dom.footer.leftBlocker))},_scrollEnabled:function(){var a=this.s.dt.settings()[0].oScroll;return""!==a.sY||""!==a.sX?!0:!1}});t.version="3.2.1";t.defaults={header:!0,footer:!1,headerOffset:0,footerOffset:0};b.fn.dataTable.FixedHeader=t;b.fn.DataTable.FixedHeader=t;b(h).on("init.dt.dtfh",function(a,c,d){"dt"===a.namespace&&(a=c.oInit.fixedHeader, +d=k.defaults.fixedHeader,!a&&!d||c._fixedHeader||(d=b.extend({},d,a),!1!==a&&new t(c,d)))});k.Api.register("fixedHeader()",function(){});k.Api.register("fixedHeader.adjust()",function(){return this.iterator("table",function(a){(a=a._fixedHeader)&&a.update()})});k.Api.register("fixedHeader.enable()",function(a){return this.iterator("table",function(c){c=c._fixedHeader;a=a!==l?a:!0;c&&a!==c.enabled()&&c.enable(a)})});k.Api.register("fixedHeader.enabled()",function(){if(this.context.length){var a=this.context[0]._fixedHeader; +if(a)return a.enabled()}return!1});k.Api.register("fixedHeader.disable()",function(){return this.iterator("table",function(a){(a=a._fixedHeader)&&a.enabled()&&a.enable(!1)})});b.each(["header","footer"],function(a,c){k.Api.register("fixedHeader."+c+"Offset()",function(d){var f=this.context;return d===l?f.length&&f[0]._fixedHeader?f[0]._fixedHeader[c+"Offset"]():l:this.iterator("table",function(e){if(e=e._fixedHeader)e[c+"Offset"](d)})})});return t}); + + +/*! + Copyright 2014-2021 SpryMedia Ltd. + + This source file is free software, available under the following license: + MIT license - http://datatables.net/license/mit + + This source file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + + For details please refer to: http://www.datatables.net + Responsive 2.2.9 + 2014-2021 SpryMedia Ltd - datatables.net/license +*/ +var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(b,k,m){b instanceof String&&(b=String(b));for(var n=b.length,p=0;ptd, >th", +g).each(function(e){e=c.column.index("toData",e);!1===a.s.current[e]&&b(this).css("display","none")})});c.on("destroy.dtr",function(){c.off(".dtr");b(c.table().body()).off(".dtr");b(k).off("resize.dtr orientationchange.dtr");c.cells(".dtr-control").nodes().to$().removeClass("dtr-control");b.each(a.s.current,function(g,l){!1===l&&a._setColumnVis(g,!0)})});this.c.breakpoints.sort(function(g,l){return g.widthl.width?-1:0});this._classLogic();this._resizeAuto();d=this.c.details;!1!== +d.type&&(a._detailsInit(),c.on("column-visibility.dtr",function(){a._timer&&clearTimeout(a._timer);a._timer=setTimeout(function(){a._timer=null;a._classLogic();a._resizeAuto();a._resize(!0);a._redrawChildren()},100)}),c.on("draw.dtr",function(){a._redrawChildren()}),b(c.table().node()).addClass("dtr-"+d.type));c.on("column-reorder.dtr",function(g,l,h){a._classLogic();a._resizeAuto();a._resize(!0)});c.on("column-sizing.dtr",function(){a._resizeAuto();a._resize()});c.on("preXhr.dtr",function(){var g= +[];c.rows().every(function(){this.child.isShown()&&g.push(this.id(!0))});c.one("draw.dtr",function(){a._resizeAuto();a._resize();c.rows(g).every(function(){a._detailsDisplay(this,!1)})})});c.on("draw.dtr",function(){a._controlClass()}).on("init.dtr",function(g,l,h){"dt"===g.namespace&&(a._resizeAuto(),a._resize(),b.inArray(!1,a.s.current)&&c.columns.adjust())});this._resize()},_columnsVisiblity:function(a){var c=this.s.dt,d=this.s.columns,f,g=d.map(function(t,v){return{columnIdx:v,priority:t.priority}}).sort(function(t, +v){return t.priority!==v.priority?t.priority-v.priority:t.columnIdx-v.columnIdx}),l=b.map(d,function(t,v){return!1===c.column(v).visible()?"not-visible":t.auto&&null===t.minWidth?!1:!0===t.auto?"-":-1!==b.inArray(a,t.includeIn)}),h=0;var e=0;for(f=l.length;eh-d[q].minWidth?(r=!0,l[q]=!1):l[q]=!0,h-=d[q].minWidth)}g=!1;e=0;for(f=d.length;e=q&&g(h,c[e].name);else if("not-"===r)for(e= +0,r=c.length;ef?c.columns().eq(0).length+f:f;if(c.cell(this).index().column!==l)return}l=c.row(b(this).closest("tr"));"click"===g.type?a._detailsDisplay(l,!1):"mousedown"===g.type?b(this).css("outline","none"):"mouseup"===g.type&&b(this).trigger("blur").css("outline","")}})}, +_detailsObj:function(a){var c=this,d=this.s.dt;return b.map(this.s.columns,function(f,g){if(!f.never&&!f.control)return f=d.settings()[0].aoColumns[g],{className:f.sClass,columnIndex:g,data:d.cell(a,g).render(c.c.orthogonal),hidden:d.column(g).visible()&&!c.s.current[g],rowIndex:a,title:null!==f.sTitle?f.sTitle:b(d.column(g).header()).text()}})},_find:function(a){for(var c=this.c.breakpoints,d=0,f=c.length;d").append(h).appendTo(g)}b("").append(l).appendTo(f);"inline"===this.c.details.type&&b(d).addClass("dtr-inline collapsed");b(d).find("[name]").removeAttr("name");b(d).css("position","relative");d=b("
").css({width:1,height:1,overflow:"hidden",clear:"both"}).append(d);d.insertBefore(a.table().node());l.each(function(e){e=a.column.index("fromVisible",e);c[e].minWidth=this.offsetWidth||0});d.remove()}},_responsiveOnlyHidden:function(){var a=this.s.dt;return b.map(this.s.current, +function(c,d){return!1===a.column(d).visible()?!0:c})},_setColumnVis:function(a,c){var d=this.s.dt;c=c?"":"none";b(d.column(a).header()).css("display",c);b(d.column(a).footer()).css("display",c);d.column(a).nodes().to$().css("display",c);b.isEmptyObject(A)||d.cells(null,a).indexes().each(function(f){y(d,f.row,f.column)})},_tabIndexes:function(){var a=this.s.dt,c=a.cells({page:"current"}).nodes().to$(),d=a.settings()[0],f=this.c.details.target;c.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]"); +"number"===typeof f?a.cells(null,f,{page:"current"}).nodes().to$().attr("tabIndex",d.iTabIndex).data("dtr-keyboard",1):("td:first-child, th:first-child"===f&&(f=">td:first-child, >th:first-child"),b(f,a.rows({page:"current"}).nodes()).attr("tabIndex",d.iTabIndex).data("dtr-keyboard",1))}});u.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];u.display={childRow:function(a,c,d){if(c){if(b(a.node()).hasClass("parent"))return a.child(d(), +"child").show(),!0}else{if(a.child.isShown())return a.child(!1),b(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();b(a.node()).addClass("parent");return!0}},childRowImmediate:function(a,c,d){if(!c&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),b(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();b(a.node()).addClass("parent");return!0},modal:function(a){return function(c,d,f){if(d)b("div.dtr-modal-content").empty().append(f());else{var g=function(){l.remove(); +b(m).off("keypress.dtr")},l=b('
').append(b('
').append(b('
').append(f())).append(b('
×
').click(function(){g()}))).append(b('
').click(function(){g()})).appendTo("body");b(m).on("keyup.dtr",function(h){27===h.keyCode&&(h.stopPropagation(),g())})}a&&a.header&&b("div.dtr-modal-content").prepend("

"+a.header(c)+"

")}}};var A={};u.renderer= +{listHiddenNodes:function(){return function(a,c,d){var f=b('
    '),g=!1;b.each(d,function(l,h){h.hidden&&(b("
  • '+h.title+"
  • ").append(b('').append(p(a,h.rowIndex,h.columnIndex))).appendTo(f),g=!0)});return g?f:!1}},listHidden:function(){return function(a, +c,d){return(a=b.map(d,function(f){var g=f.className?'class="'+f.className+'"':"";return f.hidden?"
  • '+f.title+' '+f.data+"
  • ":""}).join(""))?b('
      ').append(a):!1}},tableAll:function(a){a=b.extend({tableClass:""},a);return function(c,d,f){c=b.map(f,function(g){return"'+g.title+": "+g.data+""}).join("");return b('').append(c)}}};u.defaults={breakpoints:u.breakpoints,auto:!0,details:{display:u.display.childRow,renderer:u.renderer.listHidden(),target:0,type:"inline"},orthogonal:"display"};var C=b.fn.dataTable.Api;C.register("responsive()",function(){return this});C.register("responsive.index()", +function(a){a=b(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});C.register("responsive.rebuild()",function(){return this.iterator("table",function(a){a._responsive&&a._responsive._classLogic()})});C.register("responsive.recalc()",function(){return this.iterator("table",function(a){a._responsive&&(a._responsive._resizeAuto(),a._responsive._resize())})});C.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==b.inArray(!1,a._responsive._responsiveOnlyHidden()): +!1});C.registerPlural("columns().responsiveHidden()","column().responsiveHidden()",function(){return this.iterator("column",function(a,c){return a._responsive?a._responsive._responsiveOnlyHidden()[c]:!1},1)});u.version="2.2.9";b.fn.dataTable.Responsive=u;b.fn.DataTable.Responsive=u;b(m).on("preInit.dt.dtr",function(a,c,d){"dt"===a.namespace&&(b(c.nTable).hasClass("responsive")||b(c.nTable).hasClass("dt-responsive")||c.oInit.responsive||z.defaults.responsive)&&(a=c.oInit.responsive,!1!==a&&new u(c, +b.isPlainObject(a)?a:{}))});return u}); + + +/*! DataTables styling wrapper for Responsive + * ©2018 SpryMedia Ltd - datatables.net/license + */ + +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net-dt', 'datatables.net-responsive'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + module.exports = function (root, $) { + if ( ! root ) { + root = window; + } + + if ( ! $ || ! $.fn.dataTable ) { + $ = require('datatables.net-dt')(root, $).$; + } + + if ( ! $.fn.dataTable.Responsive ) { + require('datatables.net-responsive')(root, $); + } + + return factory( $, root, root.document ); + }; + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document, undefined ) { + +return $.fn.dataTable; + +})); + From e5349658643ea37407cf59647d6c748d0610a141 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 6 Jan 2022 22:14:30 +0100 Subject: [PATCH 31/95] datatables: add 2 pngs --- .../gstatic/datatables/img/details_close.png | Bin 0 -> 686 bytes .../webif/gstatic/datatables/img/details_open.png | Bin 0 -> 709 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 modules/http/webif/gstatic/datatables/img/details_close.png create mode 100644 modules/http/webif/gstatic/datatables/img/details_open.png diff --git a/modules/http/webif/gstatic/datatables/img/details_close.png b/modules/http/webif/gstatic/datatables/img/details_close.png new file mode 100644 index 0000000000000000000000000000000000000000..9c7d6982d9a1408f24278ed38458bcf4722f365f GIT binary patch literal 686 zcmV;f0#W^mP)rc9KHq9BMrrU)X4H-ZEQYRE1Mf85oDS6+Hm_uUD?Zq_fw zn=!^pDk~VX)hyi>=S**$PUrUR`S!lB@UYy4U- zlEcGD4Gkgv=rJ;WKMI))DAuw^dT%HXWm=_w?8Z*(BJ3CQI%#--o7^rMEd#LBM)@B$J!$?OvK`BMr zRsP$^2&f}b(NMqg?(VM1WPEB0UtKPttw@_o;Zl6jYuxh?_E**)MMmZ=lj-GUY_zo4 zX(g@tS*_O65-5I9{ZVRcW0RMmjZRMDLvyp>RI5|PAGtc61d;J^<~u33U#`0zaEJU_|e`8Yr*V+r?U8t8q~5ysN3H=`#OLdQ`{tkJnH+Sb-{!Kx_E1b-CDbv<6zhT-G!; zHrm-k^-4++%<@a!5cN&=qHLfPW&NcnA1I0WN?Z#PQ?_Tq4T=XPJ+fmXr9Rgh3WX*Z zY#nYzQEw57`)ss=4Tg%vFE(OA%kWiDnx_5WNX2@ofwe7&7_56;heB6A3S0$9q(X_2 z`oVfov$L}iHK*xu4Qu;rVd=8M;tWJwyMNQvvP;;W?hU$~2rlvFo8-^N?*Lg_D zTe3=V`<3HIr6d4&egY9GOIM|W1%+nkgGI!)I?U}_s`qE~MPPaOgl(?}8>tURI zbq@QVWUtD}o>Q37XTTcP8e{XvVEg@k7er&fzT$lU1!T4DL)K$KEZM8pr+dy|-ehDF_X77(*?9ze9%f;;Q2ML_ci!DaFdSTY^)igBw_c9}Yfb<4e2tHf re}@wNRi3XkJ+L;i)(3W7{ssI7YL=IQ_s#k*00000NkvXXu0mjfLt|7w literal 0 HcmV?d00001 From 675362ca25c044996fedd58f8eb0bf48e4cc4fb3 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 6 Jan 2022 22:15:45 +0100 Subject: [PATCH 32/95] datatables: add js file with default options including extensions, can easily be integrated into webifs by triggering --- .../gstatic/datatables/datatables.defaults.js | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 modules/http/webif/gstatic/datatables/datatables.defaults.js diff --git a/modules/http/webif/gstatic/datatables/datatables.defaults.js b/modules/http/webif/gstatic/datatables/datatables.defaults.js new file mode 100644 index 0000000000..e44b94a734 --- /dev/null +++ b/modules/http/webif/gstatic/datatables/datatables.defaults.js @@ -0,0 +1,49 @@ +/* + * This file contains a binding function with some useful datatables options + * It can be implemented in the web interface page of a plugin by using + * + */ +$(window).bind('datatables_defaults', function() { + // jQuery fix possible performance issue on scrolling + // https://stackoverflow.com/questions/39152877/ + jQuery.event.special.touchstart = { + setup: function( _, ns, handle ) { + this.addEventListener("touchstart", handle, { passive: !ns.includes("noPreventDefault") }); + } + }; + jQuery.event.special.touchmove = { + setup: function( _, ns, handle ) { + this.addEventListener('touchmove', handle, { passive: !ns.includes('noPreventDefault') }); + } + }; + try + { + // Set datatable useful defaults + $.extend( $.fn.dataTable.defaults, { + lengthMenu: [ [10, 25, 50, -1], [10, 25, 50, "All"] ], // pagination menu + pageLength: -1, // default to "all" + pagingType: "full_numbers", // include first and last in pagination menu + colReorder: true, // enable colomn reorder by drag and drop + fixedHeader: {header: true, // header will always be visible on top of page when scrolling + headerOffset: $('#webif-navbar').outerHeight() + $('#webif-tabs').outerHeight()}, + responsive: true, // enable responsive extension. + "fnDrawCallback": function(oSettings) { // hide pagination if not needed + if (oSettings._iDisplayLength > oSettings.fnRecordsDisplay() || oSettings._iDisplayLength == -1) { + $(oSettings.nTableWrapper).find('.dataTables_paginate').hide(); + } else { + $(oSettings.nTableWrapper).find('.dataTables_paginate').show(); + } + } + }); + // Set date format for correct sorting of columns containing date strings + $.fn.dataTable.moment('DD.MM.YYYY HH:mm:ss'); + } + catch (e) + { + console.log("Datatable JS not loaded, showing standard table without reorder option" + e) + } +}); From 1a4709432cfd0085c9b28d55ec2cc027936e1add Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 6 Jan 2022 22:16:30 +0100 Subject: [PATCH 33/95] datatables: add moment plugin to correctly sort columns with dates (e.g., 01.01.2022 00:00:00) --- modules/http/webif/gstatic/datatables/datetime-moment.min.js | 1 + modules/http/webif/gstatic/datatables/moment.min.js | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 modules/http/webif/gstatic/datatables/datetime-moment.min.js create mode 100644 modules/http/webif/gstatic/datatables/moment.min.js diff --git a/modules/http/webif/gstatic/datatables/datetime-moment.min.js b/modules/http/webif/gstatic/datatables/datetime-moment.min.js new file mode 100644 index 0000000000..7f19751c0e --- /dev/null +++ b/modules/http/webif/gstatic/datatables/datetime-moment.min.js @@ -0,0 +1 @@ +!function(n){"function"==typeof define&&define.amd?define(["jquery","moment","datatables.net"],n):n(jQuery,moment)}(function(n,e){function t(n){return"string"==typeof n&&(n=(n=n.replace(/(<.*?>)|(\r?\n|\r)/g,"")).trim()),n}n.fn.dataTable.moment=function(r,i,o){var a=n.fn.dataTable.ext.type;a.detect.unshift(function(n){return""===(n=t(n))||null===n?"moment-"+r:e(n,r,i,!0).isValid()?"moment-"+r:null}),a.order["moment-"+r+"-pre"]=function(n){return n=t(n),e(n,r,i,!0).isValid()?parseInt(e(n,r,i,!0).format("x"),10):o?-1/0:1/0}}}); \ No newline at end of file diff --git a/modules/http/webif/gstatic/datatables/moment.min.js b/modules/http/webif/gstatic/datatables/moment.min.js new file mode 100644 index 0000000000..57cd2d4f3d --- /dev/null +++ b/modules/http/webif/gstatic/datatables/moment.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.moment=t()}(this,function(){"use strict";var e,i;function f(){return e.apply(null,arguments)}function o(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function u(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function m(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function l(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var t in e)if(m(e,t))return;return 1}function r(e){return void 0===e}function h(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function a(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function d(e,t){for(var n=[],s=0;s>>0,s=0;sFe(e)?(r=e+1,a-Fe(e)):(r=e,a);return{year:r,dayOfYear:o}}function Ae(e,t,n){var s,i,r=Ge(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1;return a<1?s=a+je(i=e.year()-1,t,n):a>je(e.year(),t,n)?(s=a-je(e.year(),t,n),i=e.year()+1):(i=e.year(),s=a),{week:s,year:i}}function je(e,t,n){var s=Ge(e,t,n),i=Ge(e+1,t,n);return(Fe(e)-s+i)/7}C("w",["ww",2],"wo","week"),C("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),A("week",5),A("isoWeek",5),ce("w",te),ce("ww",te,Q),ce("W",te),ce("WW",te,Q),ge(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=Z(e)});function Ie(e,t){return e.slice(t,7).concat(e.slice(0,t))}C("d",0,"do","day"),C("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),C("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),C("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),C("e",0,0,"weekday"),C("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),A("day",11),A("weekday",11),A("isoWeekday",11),ce("d",te),ce("e",te),ce("E",te),ce("dd",function(e,t){return t.weekdaysMinRegex(e)}),ce("ddd",function(e,t){return t.weekdaysShortRegex(e)}),ce("dddd",function(e,t){return t.weekdaysRegex(e)}),ge(["dd","ddd","dddd"],function(e,t,n,s){var i=n._locale.weekdaysParse(e,s,n._strict);null!=i?t.d=i:y(n).invalidWeekday=e}),ge(["d","e","E"],function(e,t,n,s){t[s]=Z(e)});var Ze="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ze="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),$e="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),qe=de,Be=de,Je=de;function Qe(){function e(e,t){return t.length-e.length}for(var t,n,s,i,r=[],a=[],o=[],u=[],l=0;l<7;l++)t=_([2e3,1]).day(l),n=me(this.weekdaysMin(t,"")),s=me(this.weekdaysShort(t,"")),i=me(this.weekdays(t,"")),r.push(n),a.push(s),o.push(i),u.push(n),u.push(s),u.push(i);r.sort(e),a.sort(e),o.sort(e),u.sort(e),this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Xe(){return this.hours()%12||12}function Ke(e,t){C(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function et(e,t){return t._meridiemParse}C("H",["HH",2],0,"hour"),C("h",["hh",2],0,Xe),C("k",["kk",2],0,function(){return this.hours()||24}),C("hmm",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)}),C("hmmss",0,0,function(){return""+Xe.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),C("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),C("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ke("a",!0),Ke("A",!1),L("hour","h"),A("hour",13),ce("a",et),ce("A",et),ce("H",te),ce("h",te),ce("k",te),ce("HH",te,Q),ce("hh",te,Q),ce("kk",te,Q),ce("hmm",ne),ce("hmmss",se),ce("Hmm",ne),ce("Hmmss",se),ye(["H","HH"],Me),ye(["k","kk"],function(e,t,n){var s=Z(e);t[Me]=24===s?0:s}),ye(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),ye(["h","hh"],function(e,t,n){t[Me]=Z(e),y(n).bigHour=!0}),ye("hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s)),y(n).bigHour=!0}),ye("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i)),y(n).bigHour=!0}),ye("Hmm",function(e,t,n){var s=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s))}),ye("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[Me]=Z(e.substr(0,s)),t[De]=Z(e.substr(s,2)),t[Se]=Z(e.substr(i))});var tt=z("Hours",!0);var nt,st={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Te,monthsShort:Ne,week:{dow:0,doy:6},weekdays:Ze,weekdaysMin:$e,weekdaysShort:ze,meridiemParse:/[ap]\.?m?\.?/i},it={},rt={};function at(e){return e?e.toLowerCase().replace("_","-"):e}function ot(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return nt}function ut(t){var e;if(void 0===it[t]&&"undefined"!=typeof module&&module&&module.exports)try{e=nt._abbr,require("./locale/"+t),lt(e)}catch(e){it[t]=null}return it[t]}function lt(e,t){var n;return e&&((n=r(t)?dt(e):ht(e,t))?nt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),nt._abbr}function ht(e,t){if(null===t)return delete it[e],null;var n,s=st;if(t.abbr=e,null!=it[e])Y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=it[e]._config;else if(null!=t.parentLocale)if(null!=it[t.parentLocale])s=it[t.parentLocale]._config;else{if(null==(n=ut(t.parentLocale)))return rt[t.parentLocale]||(rt[t.parentLocale]=[]),rt[t.parentLocale].push({name:e,config:t}),null;s=n._config}return it[e]=new x(b(s,t)),rt[e]&&rt[e].forEach(function(e){ht(e.name,e.config)}),lt(e),it[e]}function dt(e){var t;if(e&&e._locale&&e._locale._abbr&&(e=e._locale._abbr),!e)return nt;if(!o(e)){if(t=ut(e))return t;e=[e]}return ot(e)}function ct(e){var t,n=e._a;return n&&-2===y(e).overflow&&(t=n[ve]<0||11xe(n[pe],n[ve])?ke:n[Me]<0||24je(n,r,a)?y(e)._overflowWeeks=!0:null!=u?y(e)._overflowWeekday=!0:(o=Ee(n,s,i,r,a),e._a[pe]=o.year,e._dayOfYear=o.dayOfYear)}(e),null!=e._dayOfYear&&(r=St(e._a[pe],s[pe]),(e._dayOfYear>Fe(r)||0===e._dayOfYear)&&(y(e)._overflowDayOfYear=!0),n=Ve(r,0,e._dayOfYear),e._a[ve]=n.getUTCMonth(),e._a[ke]=n.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=u[t]=s[t];for(;t<7;t++)e._a[t]=u[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[Me]&&0===e._a[De]&&0===e._a[Se]&&0===e._a[Ye]&&(e._nextDay=!0,e._a[Me]=0),e._d=(e._useUTC?Ve:function(e,t,n,s,i,r,a){var o;return e<100&&0<=e?(o=new Date(e+400,t,n,s,i,r,a),isFinite(o.getFullYear())&&o.setFullYear(e)):o=new Date(e,t,n,s,i,r,a),o}).apply(null,u),i=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[Me]=24),e._w&&void 0!==e._w.d&&e._w.d!==i&&(y(e).weekdayMismatch=!0)}}function Ot(e){if(e._f!==f.ISO_8601)if(e._f!==f.RFC_2822){e._a=[],y(e).empty=!0;for(var t,n,s,i,r,a,o,u=""+e._i,l=u.length,h=0,d=H(e._f,e._locale).match(N)||[],c=0;cn.valueOf():n.valueOf()"}),pn.toJSON=function(){return this.isValid()?this.toISOString():null},pn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},pn.unix=function(){return Math.floor(this.valueOf()/1e3)},pn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},pn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},pn.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},pn.isLocal=function(){return!!this.isValid()&&!this._isUTC},pn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},pn.isUtc=At,pn.isUTC=At,pn.zoneAbbr=function(){return this._isUTC?"UTC":""},pn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},pn.dates=n("dates accessor is deprecated. Use date instead.",fn),pn.months=n("months accessor is deprecated. Use month instead",Ue),pn.years=n("years accessor is deprecated. Use year instead",Le),pn.zone=n("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?("string"!=typeof e&&(e=-e),this.utcOffset(e,t),this):-this.utcOffset()}),pn.isDSTShifted=n("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!r(this._isDSTShifted))return this._isDSTShifted;var e,t={};return v(t,this),(t=bt(t))._a?(e=(t._isUTC?_:Tt)(t._a),this._isDSTShifted=this.isValid()&&0 Date: Thu, 6 Jan 2022 22:17:16 +0100 Subject: [PATCH 34/95] datatables: load default and plugin js for webif template files --- modules/http/webif/gtemplates/base.html | 13 ++++++++----- modules/http/webif/gtemplates/base_plugin.html | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) mode change 100644 => 100755 modules/http/webif/gtemplates/base.html mode change 100644 => 100755 modules/http/webif/gtemplates/base_plugin.html diff --git a/modules/http/webif/gtemplates/base.html b/modules/http/webif/gtemplates/base.html old mode 100644 new mode 100755 index 8acbc0f8d0..8c8a1ce1ba --- a/modules/http/webif/gtemplates/base.html +++ b/modules/http/webif/gtemplates/base.html @@ -25,7 +25,7 @@ - + @@ -35,12 +35,12 @@ - - + + {%- endblock styles %} - {% block scripts %} + {% block scripts %} @@ -65,7 +65,10 @@ - + + + + diff --git a/modules/http/webif/gtemplates/base_plugin.html b/modules/http/webif/gtemplates/base_plugin.html old mode 100644 new mode 100755 index c8abe5359e..f0b83ee70d --- a/modules/http/webif/gtemplates/base_plugin.html +++ b/modules/http/webif/gtemplates/base_plugin.html @@ -16,7 +16,7 @@ {% block content %} {% if scroll_heading is not defined %} -
      +
      {% endif %}
      @@ -120,7 +120,7 @@
      {{ _('Plugin') }}     : {% if p.alive %}{{_('Aktiv')}}{% else %}{{ {% endif %} -
      +
      --> - +
      From 979dd005fcc267987afdc2acf97f22d16101728f Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 8 Jan 2022 14:10:16 +0100 Subject: [PATCH 36/95] lib.orb: Set requirement to ephem max version 4.1.0 because 4.1.3 has a bug or breaking change --- lib/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/requirements.txt b/lib/requirements.txt index d522e48d99..8654e972b7 100644 --- a/lib/requirements.txt +++ b/lib/requirements.txt @@ -1,5 +1,5 @@ # lib.orb: -ephem>=3.7 +ephem>=3.7,<=4.1.0 # lib.shtime: python-dateutil>=2.5.3 From 137fdd1803b2bf445a6ed35c25d41d3bc6cd9a0f Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 9 Jan 2022 11:03:50 +0100 Subject: [PATCH 37/95] modules.websocket: Loglevel for 'no close frame received or sent' reduced to INFO --- modules/websocket/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/websocket/__init__.py b/modules/websocket/__init__.py index d9d73c3369..dfc5cb33af 100644 --- a/modules/websocket/__init__.py +++ b/modules/websocket/__init__.py @@ -562,10 +562,10 @@ async def smartVISU_protocol_v4(self, websocket): self.logger.warning("smartVISU_protocol_v4: Exception in 'await websocket.send(reply)': {} - reply = {}".format(e, reply)) except Exception as e: - if not str(e).startswith(('code = 1005', 'code = 1006')): - self.logger.error("smartVISU_protocol_v4 exception: {}".format(e)) + if not str(e).startswith(('code = 1005', 'code = 1006', 'no close frame received or sent')): + self.logger.error(f"smartVISU_protocol_v4 exception: Client {client_addr} - {e}") else: - self.logger.info("smartVISU_protocol_v4 error: {}".format(e)) + self.logger.info(f"smartVISU_protocol_v4 error: Client {client_addr} - {e}") # Remove client from monitoring dict and from dict of active clients del(self.sv_monitor_items[client_addr]) From 930bac4948c8db962a004cd7aa19b98d6321cfcd Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 17 Jan 2022 10:40:41 +0100 Subject: [PATCH 38/95] lib.scheduler: Fixed bug where value wasn't set correctly on cron triggers --- lib/scheduler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index ee2248516a..f34429a866 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -582,14 +582,15 @@ def _next_time(self, name, offset=None): if next_time is not None: if ct < next_time: next_time = ct - value = job['cron'][entry] #job['source'] = 'cron' # ms - job['source'] = {'source': 'cron1', 'details': str(entry)+' -> '+str(next_time)} + job['source'] = {'source': 'cron', 'details': str(entry)} + value = job['cron'][entry] else: next_time = ct + job['source'] = {'source': 'cron', 'details': str(entry)} value = job['cron'][entry] - job['source'] = {'source': 'cron2', 'details': str(entry)+' -> '+str(next_time)} self._scheduler[name]['next'] = next_time + self._scheduler[name]['value'] = value if name not in ['Connections', 'series', 'SQLite dump']: logger.debug("{0} next time: {1}".format(name, next_time)) From 524a38894d9fc88cf9337a705c5eb4442c2a8da8 Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 17 Jan 2022 10:42:56 +0100 Subject: [PATCH 39/95] modules.websocket: Changed loglevel for 'no close frame received' to INFO --- modules/websocket/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/websocket/__init__.py b/modules/websocket/__init__.py index dfc5cb33af..12de377674 100644 --- a/modules/websocket/__init__.py +++ b/modules/websocket/__init__.py @@ -562,10 +562,10 @@ async def smartVISU_protocol_v4(self, websocket): self.logger.warning("smartVISU_protocol_v4: Exception in 'await websocket.send(reply)': {} - reply = {}".format(e, reply)) except Exception as e: - if not str(e).startswith(('code = 1005', 'code = 1006', 'no close frame received or sent')): - self.logger.error(f"smartVISU_protocol_v4 exception: Client {client_addr} - {e}") - else: + if str(e).startswith(('code = 1005', 'code = 1006', 'no close frame received or sent')) or str(e).endswith('keepalive ping timeout; no close frame received'): self.logger.info(f"smartVISU_protocol_v4 error: Client {client_addr} - {e}") + else: + self.logger.error(f"smartVISU_protocol_v4 exception: Client {client_addr} - {e}") # Remove client from monitoring dict and from dict of active clients del(self.sv_monitor_items[client_addr]) From 5492286da32da4c494254aff24be16c8ae971412 Mon Sep 17 00:00:00 2001 From: Bernd Meiners Date: Fri, 21 Jan 2022 21:46:21 +0100 Subject: [PATCH 40/95] fix a triggertimes with empty params or wrong parameter set size --- lib/triggertimes.py | 46 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/triggertimes.py b/lib/triggertimes.py index 9faa04045c..1c2ce78d50 100644 --- a/lib/triggertimes.py +++ b/lib/triggertimes.py @@ -341,13 +341,28 @@ def __init__(self, triggertime): self.next_event = None # store last result self.max_calc_time = 0 # keep track of maximum calculation time + # prevent + self.hour = None + self.hour_range = None + self.minute = None + self.minute_range = None + self.second = None + self.second_range = None + self.day = None + self.day_range = None + self.wday = None + self.weekday_range = None + self.month = None + self.month_range = None + self.parameter_count = 0 + self._is_valid = False self.parse_triggertime() def parse_triggertime(self): """parse the crontab string for details and store them to the class variables for later use""" logger.debug(f'Enter Crontab.parse_triggertime({self._triggertime})') - self._is_valid = False + with self._lock: triggertime = self._triggertime # replace @yearly etc. with correct preset @@ -358,11 +373,14 @@ def parse_triggertime(self): try: parameter_set = triggertime.strip().split() except: - logger.error(f"crontab entry '{triggertime}' can not be split up into 4 parts for minute, hour, day and weekday") + logger.error(f"crontab entry '{triggertime}' can not be split up into parts") return False self.parameter_count = len(parameter_set) - if self.parameter_count == 4: + if self.parameter_count < 4: + logger.error(f"crontab entry '{triggertime}' has fewer than 4 parts and is invalid") + return False + elif self.parameter_count == 4: logger.debug(f'old smarthome.py style parameter set {triggertime} given') self.minute, self.hour, self.day, self.wday = parameter_set[0],parameter_set[1],parameter_set[2],parameter_set[3] self.month='*' @@ -374,6 +392,9 @@ def parse_triggertime(self): elif self.parameter_count == 6: logger.debug(f'new SmartHomeNG style parameter set {triggertime} given') self.second, self.minute, self.hour, self.day, self.month, self.wday = parameter_set[0],parameter_set[1],parameter_set[2],parameter_set[3], parameter_set[4], parameter_set[5] + else: + logger.error(f"crontab entry '{triggertime}' has more than 6 parts and is invalid") + return False if self.parameter_count > 4: # replace abbreviated months like 'jan' with their number like '1' @@ -418,6 +439,9 @@ def get_next(self, starttime: datetime): :return: found date and time of next occurence or a time way up in the future :rtype: datetime """ + if not self._is_valid: + return get_invalid_time() + with self._lock: tik = time.perf_counter() if self.next_event is None: @@ -869,10 +893,11 @@ def get_next(self, starttime: datetime): days = 0 searchtime = starttime #logger.debug(f'looking for the next event after {starttime}') - searchtime = searchtime.replace(microsecond=0) + datetime.timedelta(seconds=1) # smallest amount higher than given time + searchtime = searchtime + datetime.timedelta(microseconds=1) # smallest amount higher than given time while True: - #logger.warning(f"{searchtime}") - days = abs((starttime-searchtime).days) + #logger.warning(f"searchtime: {searchtime}") + #logger.warning(f"difference {searchtime-starttime}") + days = abs((searchtime-starttime).days) if days > days_max_count: logger.error(f'No matches after {days} examined days, giving up') return get_invalid_time() @@ -896,7 +921,12 @@ def get_next(self, starttime: datetime): # time in next_time will be in utctime. So we need to adjust it if eventtime.tzinfo == tzutc(): eventtime = eventtime.astimezone(Skytime.sh.shtime.tzinfo()) - logger.debug(f"starting with {starttime} the next {self.event}({self.doff},{self.moff}) is {eventtime}") + if starttime > eventtime: + logger.error(f"eventtime {eventtime} is earlier than starttime {starttime} maybe ephem>4.1 with errors") + #starttime = starttime.replace(hour=0,minute=0,second=0,microsecond=0)+datetime.timedelta(days=1) + continue + else: + logger.debug(f"starting with {starttime} the next {self.event}({self.doff},{self.moff}) is {eventtime}") else: logger.warning("searchtime.tzinfo was not given as utc!") else: @@ -943,7 +973,7 @@ def get_next(self, starttime: datetime): if dmax < eventtime: eventtime = dmax - # next trigger time found! + #logger.debug(f"next trigger time found: {eventtime}") searchtime = eventtime break #------------------------------ From 15aa011428ac44c35ed79b96820ee61bf225f7c1 Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 22 Jan 2022 16:42:11 +0100 Subject: [PATCH 41/95] Documentation: Consolidated logic documentation to reference section (still work in progress) --- doc/build_doc_local.sh | 8 +- doc/requirements.txt | 2 +- doc/user/Makefile | 4 + doc/user/source/beispiele/eval.rst | 22 +- doc/user/source/conf.py | 8 +- .../source/entwicklung/logiken/logiken.rst | 546 +--------------- .../konfigurationsdateien/holidays.rst | 2 +- doc/user/source/konfiguration/logging.rst | 20 +- doc/user/source/konfiguration/logiken.rst | 48 +- .../source/konfiguration/userfunctions.rst | 5 + doc/user/source/logiken/logics.rst | 591 +----------------- .../items/standard_attribute/eval.rst | 6 +- doc/user/source/referenz/logging/logging.rst | 6 +- doc/user/source/referenz/logiken/logiken.rst | 44 ++ .../referenz/logiken/logiken_funktionen.rst | 104 +++ .../logiken/logiken_grundstruktur.rst | 43 ++ .../source/referenz/logiken/logiken_items.rst | 136 ++++ .../logiken/logiken_konfiguration.rst | 231 +++++++ .../referenz/logiken/logiken_logging.rst | 56 ++ .../referenz/logiken/logiken_logic_objekt.rst | 139 ++++ .../source/referenz/logiken/logiken_mqtt.rst | 171 +++++ .../logiken/logiken_persistente_vars.rst | 140 +++++ .../logiken/logiken_python_module.rst | 22 + .../logiken/logiken_smarthomeng_methoden.rst | 169 +++++ .../logiken/logiken_standardparameter.rst | 50 ++ .../referenz/logiken/objekteundmethoden.rst | 30 + ...bjekteundmethoden_feiertage_datum_zeit.rst | 0 .../objekteundmethoden_item_methoden.rst | 0 .../logiken/objekteundmethoden_logging.rst | 0 .../logiken/objekteundmethoden_scheduler.rst | 0 .../logiken/objekteundmethoden_sonne_mond.rst | 0 .../logiken/objekteundmethoden_tools.rst | 0 .../referenz/module/module_websocket.rst | 4 +- doc/user/source/referenz/plugins/plugins.rst | 4 +- doc/user/source/referenz/referenz.rst | 4 +- doc/user/source/release/1_x_x.rst | 185 ++++++ doc/user/source/release/1_x_x.rst.vorlage | 32 +- etc/logging.yaml.default | 2 +- logics/.gitignore | 1 + logics/logic.tpl | 32 + 40 files changed, 1654 insertions(+), 1213 deletions(-) create mode 100644 doc/user/source/referenz/logiken/logiken.rst create mode 100644 doc/user/source/referenz/logiken/logiken_funktionen.rst create mode 100644 doc/user/source/referenz/logiken/logiken_grundstruktur.rst create mode 100644 doc/user/source/referenz/logiken/logiken_items.rst create mode 100644 doc/user/source/referenz/logiken/logiken_konfiguration.rst create mode 100644 doc/user/source/referenz/logiken/logiken_logging.rst create mode 100644 doc/user/source/referenz/logiken/logiken_logic_objekt.rst create mode 100644 doc/user/source/referenz/logiken/logiken_mqtt.rst create mode 100644 doc/user/source/referenz/logiken/logiken_persistente_vars.rst create mode 100644 doc/user/source/referenz/logiken/logiken_python_module.rst create mode 100644 doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst create mode 100644 doc/user/source/referenz/logiken/logiken_standardparameter.rst create mode 100644 doc/user/source/referenz/logiken/objekteundmethoden.rst rename doc/user/source/{ => referenz}/logiken/objekteundmethoden_feiertage_datum_zeit.rst (100%) rename doc/user/source/{ => referenz}/logiken/objekteundmethoden_item_methoden.rst (100%) rename doc/user/source/{ => referenz}/logiken/objekteundmethoden_logging.rst (100%) rename doc/user/source/{ => referenz}/logiken/objekteundmethoden_scheduler.rst (100%) rename doc/user/source/{ => referenz}/logiken/objekteundmethoden_sonne_mond.rst (100%) rename doc/user/source/{ => referenz}/logiken/objekteundmethoden_tools.rst (100%) create mode 100644 doc/user/source/release/1_x_x.rst create mode 100644 logics/logic.tpl diff --git a/doc/build_doc_local.sh b/doc/build_doc_local.sh index b5d306cf93..fbafed7f58 100644 --- a/doc/build_doc_local.sh +++ b/doc/build_doc_local.sh @@ -102,8 +102,8 @@ if [ "${KEEP_REPO,,}" != "true" ]; then fi echo echo Sollten die build Verzeichnisse bereits existieren, werden die alten -echo gebauten Versionen während des Skriptes gelöscht. Der Account unter dem s -echo diese Skript ausgeführt wird, muss Rechte zum anlegen von Verzeichnissen +echo gebauten Versionen während des Skriptes gelöscht. Der Account unter dem +echo dieses Skript ausgeführt wird, muss Rechte zum anlegen von Verzeichnissen echo in \'$DIR\' haben. echo if [ "${GIT_CHECKOUT,,}" == "true" ]; then @@ -171,6 +171,8 @@ if [ "${DOC,,}" == "developer" ] || [ "${DOC,,}" == "all" ]; then make html || exit echo echo Bau der Entwickler-Dokumentation ist abgeschlossen! + echo + echo Die Startseite ist: \'${DIR}/user/build/html/index.html\' fi if [ "${DOC,,}" == "user" ] || [ "${DOC,,}" == "all" ]; then @@ -185,6 +187,8 @@ if [ "${DOC,,}" == "user" ] || [ "${DOC,,}" == "all" ]; then make html || exit echo echo Bau der Anwender-Dokumentation ist abgeschlossen! + echo + echo Die Startseite ist: \'${DIR}/user/build/html/index.html\' fi diff --git a/doc/requirements.txt b/doc/requirements.txt index 07c78bf22d..469f711c28 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,5 +1,5 @@ sphinx>=3.1,<4 -sphinx-rtd-theme +sphinx-rtd-theme>=1.0 # create tabs for content for alternative ways to use like | Linux | MacOS | Windows | # https://sphinx-tabs.readthedocs.io/en/latest/ diff --git a/doc/user/Makefile b/doc/user/Makefile index cac6dedc36..7f2b8e6222 100644 --- a/doc/user/Makefile +++ b/doc/user/Makefile @@ -50,6 +50,7 @@ html: @test ! -L "source/plugins" && echo "creating a link to the plugin directory" && ln -s ../../../plugins source/plugins || echo "link to plugins directory already exists" @test ! -L "source/modules" && echo "creating a link to the module directory" && ln -s ../../../modules source/modules || echo "link to modules directory already exists" @test ! -L "source/dev" && echo "creating a link to the dev directory" && ln -s ../../../dev source/dev || echo "link to dev directory already exists" + @test ! -L "source/logics" && echo "creating a link to the logics directory" && ln -s ../../../logics source/logics || echo "link to logics directory already exists" @echo @python3 -V @python3 ../build_plugin_config_files.py de @@ -60,6 +61,7 @@ html: @test -L "source/dev" && echo "removing link to the dev directory" && unlink source/dev @test -L "source/modules" && echo "removing link to the modules directory" && unlink source/modules @test -L "source/plugins" && echo "removing link to the plugin directory" && unlink source/plugins + @test -L "source/logics" && echo "removing link to the logics directory" && unlink source/logics @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @@ -71,6 +73,7 @@ pdf: @test ! -L "source/plugins" && echo "creating a link to the plugin directory" && ln -s ../../../plugins source/plugins || echo "link to plugins directory already exists" @test ! -L "source/modules" && echo "creating a link to the module directory" && ln -s ../../../modules source/modules || echo "link to modules directory already exists" @test ! -L "source/dev" && echo "creating a link to the dev directory" && ln -s ../../../dev source/dev || echo "link to dev directory already exists" + @test ! -L "source/logics" && echo "creating a link to the logics directory" && ln -s ../../../logics source/logics || echo "link to logics directory already exists" @echo @python3 -V @python3 ../build_plugin_config_files.py de @@ -81,6 +84,7 @@ pdf: @test -L "source/dev" && echo "removing link to the dev directory" && unlink source/dev @test -L "source/modules" && echo "removing link to the modules directory" && unlink source/modules @test -L "source/plugins" && echo "removing link to the plugin directory" && unlink source/plugins + @test -L "source/logics" && echo "removing link to the logics directory" && unlink source/logics @echo @echo "Build finished. The PDF files are in build/pdf." diff --git a/doc/user/source/beispiele/eval.rst b/doc/user/source/beispiele/eval.rst index fe4e380519..db5ce48e1a 100644 --- a/doc/user/source/beispiele/eval.rst +++ b/doc/user/source/beispiele/eval.rst @@ -15,7 +15,7 @@ Hier finden sich einige Beispiele für die Anwendung von **eval** und **eval_tri Bearbeiten von Werten --------------------- -Wird einem Item ein neuer Wert zugewiesen, wird dieser erst einmal als **value** zwischengespeichert. +Wird einem Item ein neuer Wert zugewiesen, wird dieser erst einmal als **value** zwischengespeichert. Ist ein **eval** vorhanden, wird dies erst ausgeführt, bevor dem Item der Wert final zugewiesen wird. Das kann man sich für Nachbearbeitungen zu nutze machen, bspw. wenn der zugewiesene Wert zu viele Nachkommastellen hat. In folgenden Beispiel wird auf eine Nachkommastelle gerundet. @@ -30,7 +30,7 @@ Nachkommastellen hat. In folgenden Beispiel wird auf eine Nachkommastelle gerund Wertermittung durch Datenbankabfrage ------------------------------------ -Mit Hilfe eines **eval** kann auch eine Datenbankabfrage (Plugin **database**) realisiert werden. +Mit Hilfe eines **eval** kann auch eine Datenbankabfrage (Plugin **database**) realisiert werden. Im folgenden Beispiel wird täglich um 0:01 Uhr das **eval** ausgeführt und der Wert ermittelt. .. code-block:: yaml @@ -213,13 +213,13 @@ Das bool Item ist True, wenn der Wert des passenden num-Items > 0 ansonsten Fals Basierend auf dem Wert eines numerischen andere Items setzen ------------------------------------------------------------ -Bei dem folgenden Beispiel werden basierend auf dem Wert des Items "Sollzustand" die +Bei dem folgenden Beispiel werden basierend auf dem Wert des Items "Sollzustand" die Items "FolgeA", "FolgeB", "FolgeC" und "FolgeD" gesetzt. -Änderungen des Items "Sollzustand" löst für die Folgeitems den **eval_trigger** aus -und übergibt seinen Wert als "value" and diese. Im **eval** wird nun die Bedingung +Änderungen des Items "Sollzustand" löst für die Folgeitems den **eval_trigger** aus +und übergibt seinen Wert als "value" and diese. Im **eval** wird nun die Bedingung basierend auf "value" geprüft, und das Item entsprechend gesetzt. -Für das Item "FolgeA" bedeutet es konkret: Ändert sich das Wert von "Sollzustand", -wird die Neuberechnung des Items "FolgeA" angestoßen und der Wert von "Sollzustand" +Für das Item "FolgeA" bedeutet es konkret: Ändert sich das Wert von "Sollzustand", +wird die Neuberechnung des Items "FolgeA" angestoßen und der Wert von "Sollzustand" wird als "value" mit übergeben. Das eval ergibt True, wenn "value" einer 2 entspricht, ansonsten False. .. code-block:: yaml @@ -254,7 +254,7 @@ Berechnung einer Zeitdauer in Sekunden von beliebigen datetime bis jetzt In diesem Beispiel wird die Dauer eines **autotimer** mit einem **eval** aus einem **datetime** Wertes eines Hilfsitem berechnet. Die Berechnung des Item "laufzeit_autotimer" wird durch Änderungen im Item "enddatetime_autotimer" -getriggert und berechnet die Zeitdauer in Sekunden zwischen dem Wert (datetime im ISO-format) +getriggert und berechnet die Zeitdauer in Sekunden zwischen dem Wert (datetime im ISO-format) des Items "enddatetime_autotimer" und jetzt. Dieser errechnete Wert wird dann als Dauer für den **autotimer** verwendet. @@ -280,7 +280,7 @@ Dieser errechnete Wert wird dann als Dauer für den **autotimer** verwendet. Ab SmartHomeNG v1.7 gibt es hierfür eine Funktion, die in eval Attributen und Logiken verwendet werden kann: **shtime.time_since()** ermöglicht auch die Rückgabe in anderen Einheiten/Formaten als Sekunden. - Zur Verwendung der Funktionen bitte im Abschnitt Logiken unter :doc:`Feiertage, Daten und Zeiten ` + Zur Verwendung der Funktionen bitte im Abschnitt Referenz/Logiken unter :doc:`Feiertage, Daten und Zeiten ` nachschauen. Dort sind auch eine Reihe weiterer hilfreicher Funktionen beschrieben. @@ -321,7 +321,7 @@ siehe auch `Thread im knx-user-forum - - - - - - - - - - - - - - - - - - - - + + Die folgende Beispielkonfiguration definiert 4 Logiken: -* Die erste Logik mit Namen ``InitSmartHomeNG`` befindet sich in der Datei ``logics/InitSmartHomeNG.py``. +* Die erste Logik mit Namen ``InitSmartHomeNG`` befindet sich in der Datei ``logics/initsmarthomeng.py``. Das Attribut ``crontab: init`` weist SmartHomeNG an diese Logik direkt nach dem Start auszuführen. * Die zweite Logik mit dem Namen ``Hourly`` befindet sich in der Datei ``logics/time.py`` - und das Attribut ``cycle: 3600`` weist SmartHomeNG an die Logik alle 3600 Sekunden (eine Stunde) auszuführen. + und das Attribut ``cycle: 3600`` weist SmartHomeNG an die Logik alle 3600 Sekunden (jede Stunde) auszuführen. * Die dritte Logik mit Namen ``Gate`` befindet sich in der Datei ``logics/gate.py`` und das Attribut ``watch_item: gate.alarm`` weist SmartHomeNG an die Logik auszuführen, wenn der Wert des Items gate.alarm sich ändert. * Die vierte Logik mit Namen ``disks`` befindet sich in der Datei ``logics/disks.py``. Der crontab Eintrag weist SmartHomeNG an - diese Logik alle 5 Minuten auszuführen. + diese Logik alle 5 Minuten auszuführen. Der Parameter ``usage_warning`` ist ein benutzerdefinierter Parameter, der + in der Logik verwendet wird, um einen Schwellwert zu definieren. .. code-block:: yaml :caption: etc/logic.yaml InitSmarthomeNG: - filename: InitSmartHomeNG.py + filename: initsmarthomeng.py crontab: init Hourly: @@ -50,530 +62,4 @@ Die folgende Beispielkonfiguration definiert 4 Logiken: - '0,5,10,15,20,25,30,35,40,45,50,55 * * *' usage_warning: 500 -Konfigurations Parameter ------------------------- - -Die folgenden Parameter können genutzt werden um eine Logik und ihre Ausführungsumstände festzulegen: - -watch_item -~~~~~~~~~~ - -Die Liste der angegebenen Items wird auf Änderungen überwacht - -.. code-block:: yaml - :caption: etc/logic.yaml - - logicnamehere: - watch_item: - - house.alarm - - garage.alarm - - -Jede Änderung bei den Items **house.alarm** und **garage.alarm** löst die Ausführung der angegebenen Logik aus. -Es ist möglich einen Stern * für einen Pfadbestandteil zu setzen, ähnlich eines regulären Ausdrucks: - -.. code-block:: yaml - - watch_item: '*.door' - -Eine Änderung von **garage.door** oder auch **house.door** wird die Ausführung der Logik auslösen aber **nicht** -eine Änderung von **house.hallway.door** - -cycle -~~~~~ - -Sorgt für eine zyklische Ausführung der Logik - -.. code-block:: yaml - - cycle: 60 - - -Optional kann ein Argument übergeben werden - -.. code-block:: yaml - - cycle: 60 = 100 - - -Dadurch wird die Logik alle 60 Sekunden ausgeführt und der Wert 100 an die Logik übergeben. -Innerhalb der Logik kann auf den Wert über ``trigger['value']`` zugegriffen werden - -**Seit SmartHomeNG v1.3** gibt es erweiterte Konfigurations Optionen. - -Die Zeitspanne für die zyklische Ausführung kann auf zwei Arten angegeben werden: - -1. Eine Zahl die die Zeitspanne in Sekunden angibt, kann optional mit einem ``s`` gekennzeichnet werden oder -2. eine Zahl gefolgt von ``m`` die eine Zeitspanne in Minuten angibt - -.. role:: bluesup - -crontab :bluesup:`Update` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. Der Inhalt der Beschreibung von crontab wurde aus referenz/items/standard_attribute/crontab.rst 1:1 kopiert - -Es gibt drei verschiedene Parametersätze für ein Crontab Attribut: - -.. tabs:: - - .. tab:: init - Das Item wird zum Start von SmarthomeNG aktualisiert und triggert - dadurch unter Umständen eine zugewiesene Logik: - - .. code-block:: yaml - - crontab: init - - Hier kann auch zusätzlich ein Offset angegeben werden um den - tatsächlichen Zeitpunkt zu verschieben: - - .. code-block:: yaml - - crontab: init+10 # 10 Sekunden nach Start - - .. tab:: Zeitpunkte - - Das Item soll zu bestimmten Zeitpunkten aktualisiert werden. - Die Schreibweise ist an Linux Crontab angelehnt, entspricht diesem aber nicht genau. - Es gibt je nach Parameteranzahl 3 Varianten: - - * ``crontab: `` - * ``crontab: `` - * ``crontab: `` - - Dabei sind je nach Variante folgende Werte zulässig: - - * Sekunde: ``0`` bis ``59`` - * Minute: ``0`` bis ``59`` - * Stunde: ``0`` bis ``23`` - * Tag: ``1`` bis ``31`` - * Monat: 1 bis 12 oder ``jan`` bis ``dec`` - * Wochentag ``0`` bis ``6`` oder ``mon``, ``tue``, ``wed``, ``thu``, ``fri``, ``sat``, ``sun`` - - Alle Parameter müssen durch ein Leerzeichen getrennt sein und innerhalb eines Parameters - darf kein zusätzliches Leerzeichen vorhanden sein, sonst kann der Parametersatz nicht ausgewertet werden. - - Im folgenden Beispiel wird jeden Tag um 23:59 ein Trigger erzeugt und der Wert 70 gesetzt. - - .. code-block:: yaml - - crontab: 59 23 * * = 70 - - Für jede dieser Zeiteinheiten (Minuten, Stunde, Tag, Wochentag) werden - folgende Muster unterstützt (Beispiel jeweils ohne Anführungszeichen verwenden): - - * eine einzelne Zahl, z.B. ``8`` → immer zur/zum 8. Sekunde/Minute/Stunde/Tag/Wochentag - * eine Liste von Zahlen, z.B. ``2,8,16`` → immer zur/zum 2., 8. und 16. Sekunde/Minute/Stunde/Tag/Monat/Wochentag - * ein Wertebereich, z.B. ``1-5`` → immer zwischen dem/der 1. und 5. Sekunde/Minute/Stunde/Tag/Monat/Wochentag - * einen Interval, z.B. ``\*\/4`` → immer alle 4 Sekunden/Minuten/Stunden/Tage/Wochentage - * einen Stern, z.B. ``*`` → jede Sekunde/Minute/Stunde/Tag/Monat/Wochentag - - .. tab:: Zeitpunkte bezogen auf Aufgang von Sonne oder Mond - - Nach dem Muster ``[H:M<](sunrise|sunset|moonrise|moonset)[+|-][offset][ )`` kann ein Triggerpunkt bezogen - auf Sonne oder Mond berechnet werden: - - * ``sunrise`` → immer zum Sonnenaufgang - * ``sunset`` → immer zum Sonnenuntergang - * ``sunrise`` und untere Begrenzung → ``06:00`` zusätzlich geladen werden. - - - -Vorhandene Objekte und Methoden -------------------------------- - -Ausser dem Smarthome Objekt ``sh`` gibt es weitere vordefinierte Objekte und Methoden innerhalb von Logiken - -logic -~~~~~ - -Dieses Objekt bietet Zugriff auf das aktuelle Logik Objekt. -Es ist möglich die Attribute wie ``crontab``, ``cycle``, etc. während der Laufzeit zu ändern. -Die Änderungen werden aber nicht in die ``logic.yaml`` geschrieben und sind nach einem -Neustart von SmartHomeNG verloren. - -``logic.alive`` - Der Code ``while logic.alive:`` erzeugt eine Endlos-Schleife die bis zum Beenden - von SmartHomeNG läuft. - -``logic.name``: - Liefert den Namen der Logik wie in ``logic.yaml`` definiert - -``logic.last\_time()``: - Diese Funktion liefert die letzte Ausführungszeit der Logik vor dem aktuellen Aufruf - -``logic.prio``: - Lesen und setzen der Priorität dieser Logik - -``logic.trigger()``: - Wie ``sh.trigger()``, aber triggert nur die aktuelle Logik. Diese Funktion ist nützlich - um die Logik zu einem späteren Zeitpunkt (noch einmal) auszuführen - -trigger -~~~~~~~ - -Das Dictionary ``trigger`` liefert Informationen zur Laufzeit der Logik -über das Ereignis, das zum Aufruf der Logik geführt hat. - -``trigger['by']``: - Ursprung - -``trigger['source']`` - Quelle - -``trigger['dest']``: - Ziel - -``trigger['value']``: - Wert - -logger und sh.log ------------------ - -Das ``logger`` Objekt wird zum Generieren von Log Mitteilungen verwendet. -Es stellt fünf unterschiedliche Logging Einstufungen (level) bereit: -``debug``, ``info``, ``warning``, ``error`` und ``critical``. - -Die Angabe ``logger.info('42')`` schreibt einen Eintrag ``42`` in den zugehörigen Logger. -Damit Logging Einträge auch in Logs auftauchen, muß in der ``logging.yaml`` -eine entsprechende Logging Einstellung vorhanden sein. -Die Grundeinstellung in der ``logging.yaml`` sorgt dafür, das die Einstufungen -``warning``, ``error`` und ``critical`` aufgezeichnet werden. - -.. attention:: - - Die Zeitangabe in den Log Mitteilungen beziehen sich immer auf die lokal eingestellte Zeit - -SmartHomeNG Methoden --------------------- - -sh.now und sh.utcnow -~~~~~~~~~~~~~~~~~~~~ - -Diese beiden Funktionen geben ein ``datetime`` Objekt zurück das die lokale Zeitzone berücksichtigt. -Es ist möglich mit anderen Zeitzonen zu rechnen. -``sh.tzinfo()`` und ``sh.utcinfo()`` geben die lokale Zeitzone und die UTC Zeitzone zurück. - -sh.sun -~~~~~~ - -Dieses Modul gibt Zugriff auf das Sonnenobjekt. -Vorbedingung ist die Definition von Längen- und Breitengrad in ``smarthome.yaml``. - -``sh.sun.pos([offset], [degree=False])`` - Gibt die aktuelle Position der Sonne zurück, optional einen Offset in Minuten and und - ob der Rückgabe in Grad anstatt in Rad erfolgen soll - - ``azimut, altitude = sh.sun.pos()``: - liefert die aktuelle Position der Sonne - - ``azimut, altitude = sh.sun.pos(degree=True)``: - liefert die aktuelle Position der Sonne in Grad - - ``azimut, altitude = sh.sun.pos(30)`` - liefert die Position, die die Sonne in 30 Minuten haben wird - -``sh.sun.set([offset])``: - Gibt den nächsten Sonnenuntergang zurück, optional mit einem Offset in Grad. - - ``sunset = sh.sun.set()``: - Liefert ein auf UTC basierendes ``datetime`` Objekt mit dem nächsten Sonnenuntergang - - ``sunset_tw = sh.sun.set(-6)``: - Liefert ein auf UTC basierendes ``datetime`` Objekt mit der Zeitangabe - des nächsten Sonnenuntergangs zuzüglich der Zeit bis die Dämmerung beendet ist. - -``sh.sun.rise([offset])``: - Gibt analog zu ``set`` den nächsten Sonnenaufgang zurück, optional mit einem Offset in Grad. - - -sh.moon -~~~~~~~ - -Neben den drei Funktionen ``pos``, ``set`` und ``rise`` (wie beim Objekt ``sh.sun``) gibt es noch -zwei weitere Funktionen: - -``sh.moon.light(offset)``: - liefert einen Wert im Bereich [0...100] der hellen Oberfläche zur aktuellen Zeit plus einen Offset - -``sh.moon.phase(offset)``: - Liefert die Mondphase als Ganzzahl Wert im Bereich [0...7]: - 0 = Neumond - 4 = Vollmond - 7 = abnehmender Halbmond - - -Scheduler ---------- - -sh.scheduler.trigger() / sh.trigger() -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Diese globale Funktion triggert eine gegebene Logik unter Angabe ihres Namens - -``sh.trigger(name [, by] [, source] [, value] [, dt])`` - ``name``: - der Name der zu triggernden Funktion - - ``by``: - Name einer aufrufenden Logik, der Vorgabewert ist ``Logic`` - - ``source``: - der Grund für das triggern - - ``value`` - eine Variable - - ``dt``: - ein datetime Objekt das die Triggerzeit angibt (lokale Zeitzone berücksichtigt) - -sh.scheduler.change() -~~~~~~~~~~~~~~~~~~~~~ - -Diese Methode ändert Optionen zur Laufzeit der Logiken. Beispiel: - -``sh.scheduler.change('alarmclock', active=False)`` deaktiviert die Logik ``alarmclock`` - -Zusätzlich zum ``active`` parameter können auch ``cron`` und ``cycle`` geändert werden. - -sh.tools Objekt ---------------- - -Das ``sh.tools`` Objekt stellt folgende nützliche Funktionen zur Verfügung: - -sh.tools.ping() -~~~~~~~~~~~~~~~ - -Sendet ein Ping an einen Computer und liefert das Ergebnis. Beispiel: - -``sh.office.laptop(sh.tools.ping('hostname'))`` - -setzt das Item ``office.laptop`` entsprechend der Rückmeldung ob ein Ping erfolgreich war oder nicht. - -sh.tools.dewpoint() -~~~~~~~~~~~~~~~~~~~ - -Berechnet den Taupunkt für eine gegebene Temperatur und Feuchtigkeit. Beispiel: - -``sh.office.dew(sh.tools.dewpoint(sh.office.temp(), sh.office.hum())`` - -setzt das Item ``office.dew`` auf das Ergebnis der Taupunktberechnung der Itemwerte von ``office.temp`` und ``office.hum`` - -sh.tools.fetch\_url() -~~~~~~~~~~~~~~~~~~~~~ - -Liefert dem Inhalt einer Webseite als String oder ``False`` wenn ein Fehler auftritt. - -``sh.tools.fetch_url('https://www.regular.com')`` - -Es ist möglich als Parameter den Benutzernamen und ein Password anzugeben um die Abfrage bei der zu authentifizieren. - -``sh.tools.fetch_url('https://www.special.com', 'username', 'password')`` - -Weiterhin kann ein Parameter für eine Zeitüberschreitung bestimmt werden: - -``sh.tools.fetch_url('https://www.regular.com', timeout=4)`` - -bricht nach 4 Sekunden ohne Ergebnis ab - -sh.tools.dt2ts(dt) -~~~~~~~~~~~~~~~~~~ - -Wandelt ein datetime Objekt in einen Unix Zeitstempel um. - -sh.tools.dt2js(dt) -~~~~~~~~~~~~~~~~~~ - -Wandelt ein datetime Objekt in einen json Zeitstempel um. - - -sh.tools.rel2abs(temp, hum) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Wandelt einen relativen Feuchtigkeitswert in einen absoluten Feuchtigkeitswert um. - -Zugriffe auf Items ------------------- - -Die Nutzung des ``sh`` Objektes für Items wird nicht weitergeführt. Es ist besser das Item API wie folgt zu nutzen: - -.. code:: python - - from lib.item import Items - items = Items.get_instance() - -Mit dem ``items`` Objekt können nun die folgenden Funktionen verwendet werden: - -items.return_item(path) -~~~~~~~~~~~~~~~~~~~~~~~ - -Liefert ein Item Objekt für den angegebenen Pfad zurück. Beispiel: - -``items.return_item('first_floor.bath')`` - -items.return_items() -~~~~~~~~~~~~~~~~~~~~ - -Liefert alle Item Objekte zurück - -.. code-block:: python - - for item in items.return_items(): - logger.info(item.id()) - -items.match_items(regex) -~~~~~~~~~~~~~~~~~~~~~~~~ - -Liefert alle Item Objekte deren Pfad mit einem regulären Ausdruck gefunden wird und die optional ein bestimmtes Attribut aufweisen. - -.. code-block:: python - - for item in items.match_items('*.lights'): # selects all items ending with 'lights' - logger.info(item.id()) - - for item in items.match_items('*.lights:special'): # selects all items ending with 'lights' and attribute 'special' - logger.info(item.id()) - -items.find_items(configattribute) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Abhängig von ``configattribute`` werden die folgenden Items zurückgegeben: - -.. table:: - - ====================== ========================================================= - Attribut Ergebnis - ====================== ========================================================= - ``attribute`` Nur Items bei denen keine Instanz ID angegeben ist - ``attribute@`` Items mit oder ohne Instanz ID - ``attribute@instance`` Items mit einem bestimmten Attribut und einer Instanz ID - ``@instance`` Items mit einer bestimmten Instanz ID - ====================== ========================================================= - - -.. code:: python - - for item in items.find_items('my_special_attribute'): - logger.info(item.id()) - -find\_children(parentitem, configattribute): -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Liefert alle Kind Item Objekte eines Elternitems mit einem gegebenen ``configattribute``. -Die Suche nach dem ``configattribute`` wird genauso durchgeführt wie in ``find_items(configattribute)`` weiter oben. diff --git a/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst b/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst index b1bec1e26c..8004b123b4 100644 --- a/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst +++ b/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst @@ -94,6 +94,6 @@ etwa so aussehen: :language: yaml -Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` +Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` beschrieben, wie die hier konfigurierten Feiertage in Logiken und **eval** Attributen genutzt werden können. diff --git a/doc/user/source/konfiguration/logging.rst b/doc/user/source/konfiguration/logging.rst index a8a34bd973..5db8f07251 100644 --- a/doc/user/source/konfiguration/logging.rst +++ b/doc/user/source/konfiguration/logging.rst @@ -1,6 +1,6 @@ :tocdepth: 2 -..index:: Logging +.. index:: Logging ####### Logging @@ -10,22 +10,22 @@ Ein `Log `_ zeichnet Ergebnisse von Vorgänge und dient der Dokumentation. Anhand eines Logs kann man Programmfehlern auf die Spur kommen oder bestimmte Situationen können im Nachhinein untersucht werden. Je detaillierter ein Log geführt wird, desto einfacher ist die Untersuchung bestimmter Sachverhalte. -Je nachdem, was man untersuchen möchte, kann man mit einem **Logging Level** im Programm vorgeben wie ernst +Je nachdem, was man untersuchen möchte, kann man mit einem **Logging Level** im Programm vorgeben wie ernst oder wie wichtig ein bestimmter Logeintrag ist. -Innerhalb des Kerns von SmartHomeNG finden sich zum Beispiel Einträge im Programm mit dem Log Level **NOTICE** +Innerhalb des Kerns von SmartHomeNG finden sich zum Beispiel Einträge im Programm mit dem Log Level **NOTICE** die in einer Logdatei dann im Ergebnis so aufgezeichnet werden: ``2021-04-16 21:56:31 NOTICE lib.smarthome -------------------- Init SmartHomeNG 1.8.2c.4e0938c2.develop --------------------`` -Das ist als Information zu sehen um bei Problemen Hilfe zu erhalten. Es deutet hier nichts auf Fehler oder Probleme hin. +Das ist als Information zu sehen um bei Problemen Hilfe zu erhalten. Es deutet hier nichts auf Fehler oder Probleme hin. Ein anderer Logging Befehl im Core mit dem Log Level **WARNING** erzeugt hingehen folgendes: ``2021-04-16 21:56:32 WARNING lib.module Not loading module Mqtt from section 'mqtt': Module is disabled`` Das ist als Warnung gedacht um darauf hinzuweisen, das ein Module nicht geladen wird und in dieser Folge eventuell weitere Fehler oder Probleme auftauchen könnten. Steigerungen von Warnungen sind Log Level **ERROR** oder **CRITICAL**. -Während ein **ERROR** also ein Fehler durchaus bedeuten kann das SmartHomeNG weiterarbeiten kann, bedeutet ein **CRITICAL** -also ein kritischer Fehler das das Programm beendet werden muss. +Während ein **ERROR** also ein Fehler durchaus bedeuten kann das SmartHomeNG weiterarbeiten kann, bedeutet ein **CRITICAL** +also ein kritischer Fehler das das Programm beendet werden muss. Fehlt ein für den Kern von SmartHomeNG benötigtes Modul, so stell das einen kritischen Fehler dar. Die Log Level in der Übersicht, absteigend in der Bedeutung für den Programmablauf: @@ -51,7 +51,7 @@ Die Log Level in der Übersicht, absteigend in der Bedeutung für den Programmab - Warnung das etwas unerwartetes passiert ist aber trotzdem weitergearbeitet werden kann * - 20 - INFO - - Eine Ablaufinformation die nicht unbedingt wichtig ist + - Eine Ablaufinformation die nicht unbedingt wichtig ist * - DEBUG - 10 - Informationen für die Fehlersuche die normalerweise nicht benötigt werden @@ -68,14 +68,14 @@ verstanden werden sollen. Konfiguration des Loggings ========================== -Auf der Seite `Python Logging `_ +Auf der Seite `Python Logging `_ sind die Konfigurationsmöglichkeiten detailliert beschrieben. SmartHomeNG lädt beim Start die Konfiguration des Logging aus der Datei **etc/logging.yaml**. Ist diese Datei nicht vorhanden, -so versucht SmartHomeNG die Datei **etc/logging.yaml.default** zu kopieren nach **etc/logging.yaml** und dann daraus +so versucht SmartHomeNG die Datei **etc/logging.yaml.default** zu kopieren nach **etc/logging.yaml** und dann daraus die Konfiguration des Loggings zu laden. -Wenn bei der Konfiguration des Loggings etwas schief geht, kann also jederzeit die Datei **etc/logging.yaml** gelöscht oder +Wenn bei der Konfiguration des Loggings etwas schief geht, kann also jederzeit die Datei **etc/logging.yaml** gelöscht oder besser umbenannt werden und wird dann beim nächsten Neustart durch den Inhalt der **etc/logging.yaml.default** frisch bereitgestellt. Ein Beispiel für **etc/logging.yaml.default** im Folgenden: diff --git a/doc/user/source/konfiguration/logiken.rst b/doc/user/source/konfiguration/logiken.rst index 8fa285a1eb..31c0dd4457 100644 --- a/doc/user/source/konfiguration/logiken.rst +++ b/doc/user/source/konfiguration/logiken.rst @@ -1,8 +1,8 @@ .. index:: Logiken; Konfiguration -####### +======= Logiken -####### +======= Zur Konfiguration einer Logik wird in der Datei **../etc/logic.yaml** ein Abschnitt für die Logik angelegt. Unter dem Namen dieses Abschnitts wird die Logik an anderen Stellen referenziert. @@ -10,46 +10,8 @@ Logik angelegt. Unter dem Namen dieses Abschnitts wird die Logik an anderen Stel In diesem Abschnitt muss SmartHomeNG mitgeteilt werden, welche Code Datei ausgeführt werden soll und unter welchen Umständen. Dazu werden die im Folgenden beschriebenen Parameter genutzt. +.. include:: /referenz/logiken/logiken_standardparameter.rst -Standard Parameter ------------------- - -+------------------+-----------------------------------------------------------------------------------------------+ -| **Parameter** | **Beschreibung** | -+==================+===============================================================================================+ -| filename | Dateiname des Logik-Codes. Diese Datei muss im Verzeichnis **../logics** liegen. Dieser | -| | Parameter muss angegeben werden. | -+------------------+-----------------------------------------------------------------------------------------------+ -| watch_item | **Optional**: String oder Liste von Strings, die jeweils einen Item-Pfad repräsentieren. | -| | Eine Veränderung eines der hier aufgeführten Items führt dazu, dass die Logik ausgeführt | -| | wird. | -+------------------+-----------------------------------------------------------------------------------------------+ -| crontab | **Optional**: String oder Liste von Strings, die einen crontab Eintrag darstellen. Der Syntax | -| | des Parameters entspricht dem Syntax des **crontab** Attributes von Items. Details dazu | -| | stehen :doc:`hier ` . | -+------------------+-----------------------------------------------------------------------------------------------+ -| cycle | **Optional**: Angabe einer Zykluszeit, die angibt, in welchem Zeitabstand die Logik | -| | periodisch ausgelöst werden soll. Der Syntax des Parameters entspricht dem Syntax des | -| | **cycle** Attributes von Items. Details dazu stehen | -| | :doc:`hier ` . | -+------------------+-----------------------------------------------------------------------------------------------+ -| prio | **Optional**: Angabe einer Priorität für die Logik. Die Priorität kommt nur bei Logiken zum | -| | Einsatz, die ein Schedule haben, bei denen also der Paramter **crontab** oder **cycle** | -| | angegeben wurde. Die Priorität sollte zwischen **2** und **6** liegen. Falls der Parameter | -| | nicht angegeben wird, wird die Standardpriorität **3** verwendet. | -+------------------+-----------------------------------------------------------------------------------------------+ -| visu_acl | **Optional**: Dieser Parameter wird durch das Plugin **visu_websocket** | -| | implementiert. Wenn dieser Parameter auf **True** gesetzt wird, kann die Logik | -| | von einer Visualisierung aus (z.B. smartVISU) ausgelöst werden. | -+------------------+-----------------------------------------------------------------------------------------------+ -| | **Optional**: Es können weitere Parameter definiert werden, diese können aus der | -| | Logik heraus abgefragt werden und haben sonst keine Funktion. | -+------------------+-----------------------------------------------------------------------------------------------+ - -Falls keiner der optionalen Parameter **crontab**, **watch_item** oder **cycle** angegeben wird, wird -die Logik nicht automatisiert ausgeführt. Sie kann dann nur aus Plugins oder (falls konfiguriert) über -eine Visualisierung ausgelöst werden. - - -Details zur Erstellung von Logiken finden sich :doc:`hier <../logiken/logics>` . +Weitere Informationen und Details zur Erstellung von Logiken finden sich unter :doc:`Referenz/Logiken ` +und unter :doc:`Entwicklung/Logiken `. diff --git a/doc/user/source/konfiguration/userfunctions.rst b/doc/user/source/konfiguration/userfunctions.rst index a8389aa69c..dc05e21182 100644 --- a/doc/user/source/konfiguration/userfunctions.rst +++ b/doc/user/source/konfiguration/userfunctions.rst @@ -3,6 +3,11 @@ .. role:: greensup .. role:: redsup +.. index:: Konfiguration; Userfunctions +.. index:: Konfiguration; uf +.. index:: Konfiguration; Functions +.. index:: Userfunctions; Konfiguration + =========================== Userfunctions :redsup:`Neu` =========================== diff --git a/doc/user/source/logiken/logics.rst b/doc/user/source/logiken/logics.rst index b9ee90bd8b..902c7bad0f 100644 --- a/doc/user/source/logiken/logics.rst +++ b/doc/user/source/logiken/logics.rst @@ -12,595 +12,26 @@ Logiken ####### -Logiken für SmartHomeNG sind Python Skripte. Zur Erstellung von Logiken müssen Sie über -Kenntnisse der Programmiersprache Python verfügen. +Logiken dienen dazu, komplexere Zustände und Abläufe zu steuern, die über Item Attribute nicht abgebildet werden +können. Logiken für SmartHomeNG sind Python Skripte. Zur Erstellung von Logiken müssen Sie über Kenntnisse +der Programmiersprache Python verfügen. -Die Logik-Skripte müssen im Verzeichnis **../logics** der SmartHomeNG Installation abgelegt werden. - -===================== -Grundlegende Struktur -===================== - -Das wichtigste Objekt, das in Logiken verwendet wird, ist **sh**. Dies ist das Smarthome-Objekt. -Es enthält jedes Detail über die laufende SmartHomeNG Instanz. Mit diesem Objekt ist es möglich auf -alle Items, Plugins und Grundfunktionen von SmartHomeNG zuzugreifen. - -Zugriff auf Items und Werte -=========================== - -Um den Wert eines Items zu erhalten, rufen Sie zum Beispiel den Namen auf: sh.path.item(). -Um einen neuen Wert zu setzen, geben Sie ihn einfach als Argument an: sh.path.item(neuer_wert). - -.. attention:: - - Zuweisung von Item-Werten: - - Es ist sehr wichtig, immer mit Klammern **()** auf die Items zuzugreifen! Wenn das Item direkt - zugewiesen wird, z.B. mit sh.path.item = Wert, dann wird das item-Objekt in SmartHomeNG überschrieben. - - In diesem Fall kann die mitgelieferte Logik **check_items.py** verwendet werden, um auf Vorhandensein - entsprechend beschädigter Items zu prüfen und diese wiederherzustellen. Alternativ werden die Items nach - einem Neustart von SmartHomeNG neu erstellt. - - -Alternativ kann auch über die Item-Properties auf den Wert zugegriffen werden: sh.path.item.propery.value -gibt den Wert zurück, mit sh.path.item.property.value = Wert kann der Wert zugewiesen werden. Diese Variante -lässt sich wie eine normale Variablenzuweisung nutzen. - -Beispiel -======== - -Eine Logik sieht prinzipiell folgendermaßen aus: - -.. code-block:: python - :caption: /usr/local/smarthome/logics/testlogik1.py - - #!/usr/bin/env python3 - # testlogik1.py - - #Code der Logik: - - # Das Deckenlicht im Büro einschalten, falls es nicht eingeschaltet ist - if not sh.buero.deckenlicht(): - sh.buero.deckenlicht('on') - - -=============================== -Verfügbare Objekte und Methoden -=============================== - - -.. toctree:: - :maxdepth: 3 - :hidden: - :titlesonly: - - objekteundmethoden_logging - objekteundmethoden_feiertage_datum_zeit - objekteundmethoden_sonne_mond - objekteundmethoden_item_methoden - objekteundmethoden_scheduler - objekteundmethoden_tools - - -Neben dem **sh** Objekt, gibt es andere wichtige vordefinierte Objekte: - - -Das logic Objekt -================ - -Dieses Objekt bietet Zugriff auf das aktuelle Logikobjekt. Es ist möglich, während der Laufzeit -logische Attribute (crontab, cycle, ...) abzufragen und ändern. Diese Änderungen gehen nach dem Neustart -von SmartHomeNG verloren. - -Definierte Methoden des Logikobjekts: - -+-------------------+--------------------------------------------------------------------------------------------------------+ -| Methode | Erläuterung | -+===================+========================================================================================================+ -| logic.id() | Diese Methode liefert dem Namen der Logik wie in **../etc/logic.yaml** angegeben. | -+-------------------+--------------------------------------------------------------------------------------------------------+ -| logic.last_run() | Diese Methode liefert den letzten Lauf dieser Logik (vor aktuellen Lauf). | -+-------------------+--------------------------------------------------------------------------------------------------------+ -| logic.disable() | Konfigurierte Logiken sind standardmäßig aktiv und werden entsprechend der Konfiguration ausgeführt. | -| | Diese Methode deaktiviert die Logik, sodass deren Ausführung unterbunden wird. (Ab SmartHomeNG v1.3) | -+-------------------+--------------------------------------------------------------------------------------------------------+ -| logic.enable() | Eine bereits deaktivierte Logik kann mit dieser Methode wieder aktiviert werden. (Ab SmartHomeNG v1.3) | -+-------------------+--------------------------------------------------------------------------------------------------------+ - - -Vordefinierte Attribute des Logikobjekts: - -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| Attribut | Erläuterung | -+===========================+========================================================================================================+ -| trigger[] | Ein Python-Dictionary, welches im Folgenden beschreiben wird. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.name | Das Attribut logic.name liefert das selbe Ergebnis wie die Methode logic.id() | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.crontab | Das Attribut liefert das aktuelle **crontab** Setting dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.cycle | Das Attribut liefert das aktuelle **cycle** Setting dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.prio | Das Attribut liefert das aktuelle **prio** Setting dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.filename | Das Attribut liefert den Dateinamen des Python Skripts dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic. | Liefert den konfigurierten Parameter oder den Wert einer in einem vorherigen Lauf dieser | -| | Logik persistieren Variablen. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ - - -trigger Dict -============ - -trigger ist ein Python-Dictionary, welches als Laufzeitumgebung einige Informationen über das -Ereignis liefert, das die Logik ausgelöst hat. - -Das Dictionary enthält folgende Informationen: - -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| Attribut/Funktion | Erläuterung | -+===========================+========================================================================================================+ -| trigger['by'] | Auslöser ('Scheduler', Item', etc.) | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['source'] | enthält den Pfad des Items, welches die Logik getriggert hat. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['source_details'] | Falls eine Logik aus einem Item heraus getriggert wurde (also trigger['by'] == Item ist), enthält | -| | trigger['source_details'] weitere Details zum Auslöser (Beispiel: 'knx:1.1.241:ga=3/3/5') | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['dest'] | | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['value'] | enthält den Wert des Items, dass die Logik getriggert hat. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ - - -Das logics Objekt -================= - -Zugriff auf das Logics-API über das logics Objekt: - -+---------------------------------+---------------------------------------------------------------------------------------------------------+ -| Methode | Erläuterung | -+=================================+=========================================================================================================+ -| logics. | ermöglicht den Zugriff auf das Logics API, welches in der Developer Dokumentation beschrieben ist. | -| | Im folgenden sind einige Beispiele aufgeführt: | -+---------------------------------+---------------------------------------------------------------------------------------------------------+ -| logics.scheduler_add() | Hinzufügen eines Scheduler Eintrages für den logics-Namensraum. Der Syntax entspricht der | -| | scheduler.add() Methode. | -+---------------------------------+---------------------------------------------------------------------------------------------------------+ -| logics.scheduler_change() | Ändern eines Scheduler Eintrages im logics-Namensraum. Der Syntax entspricht der scheduler.change() | -| | Methode. | -+---------------------------------+---------------------------------------------------------------------------------------------------------+ -| logics.scheduler_remove() | Löschen eines Scheduler Eintrages im logics-Namensraum. Der Syntax entspricht der scheduler_remove() | -+---------------------------------+---------------------------------------------------------------------------------------------------------+ -| logics.trigger_logic() | Triggern einer im Logik | -+---------------------------------+---------------------------------------------------------------------------------------------------------+ -| logics.set_config_section_key() | Setzt den Wert eines Schlüssels für eine angegebene Logik (Abschnitt) permanent in ../etc/logic.yaml | -+---------------------------------+---------------------------------------------------------------------------------------------------------+ - -Der vollständige Syntax der Methoden kann der `Entwickler Dokumentation `_ entnommen werden. - - -Geladene Python Module -====================== - -Im Logik Environment sind diverse Python Module bereits geladen: - -- sys -- os -- time -- datetime -- ephem -- random -- Queue -- subprocess - - -============= -Konfiguration -============= - -Details zur Konfiguration von Logiken finden sich :doc:`hier <../konfiguration/logiken>` . - - -.. index:: Logiken; Persistente Variablen - -===================== -Persistente Variablen -===================== - -Normale Variablen innerhalb von Logiken sind nur für den jeweiligen Lauf gültig. Es ist jedoch -in einigen Fällen notwendig, Werte zwischen verschiedenen Läufen einer Logik zu übergeben. - -Solche persistente Variablen sind in Logiken von SmartHomeNG möglich, es sind jedoch einige -Dinge zu beachten: - -- Diese Variablen sind über mehrere Läufe von Logiken persistent. Allerdings gilt das nur während - des Laufs von SmartHomeNG. Bei einem Neustart von SmartHomeNG gehen diese Werte verloren. - Der Wert einer solchen Variablen geht auch verloren, wenn die Logik über das Backend während - der Laufzeit von SmartHomeNG gespeichert und neu geladen wird. -- Beim 1. Lauf einer Logik nach dem Start von SmartHomeNG existieren diese Variablen nicht. Der - erste Zugriff innerhalb einer Logik muss deshalb in einen **if not hasattr():** Ausdruck - eingebunden werden. -- Diese Variablen sind **lokal zur Logik**. Sie stehen außerhalb der Logik, die sie definiert hat, - nicht zur Verfügung. Wenn die Daten auch außerhalb der Logik verwendet werden, müssen sie in - Items abgelegt werden. (Ein Sonderfall sind Werte, die zwar außerhalb der definierenden Logik - verwendet werden sollen, aber nur in Logiken. Hier gibt es eine weitere Möglichkeit, die - weiter unten beschrieben wird). -- Es dürfen einige Namen nicht verwendet werden, da sie interne Variablen des Logik Objektes - überschreiben würden. - -Die Persistenz dieser Variablen gilt nur während des Laufs von SmartHomeNG. Mit der Beendigung von -SmartHomeNG gehen die gespeicherten Werte verloren. - - -Einrichtung -=========== - -Normale Variablen sind lokal zum Lauf der der Logik. Eine Variable **myvar** die während eines -Laufes der Logik einen Wert zugewiesen bekommt, ist beim Beginn des nächsten Laufes nicht -definiert. - -Um eine Variable so zu definieren, dass sie die Zeit bis zum nächsten Lauf der Logik überdauert, -muss sie als Attribut zur Logik definiert werden. Also statt: - -.. code-block:: python - - myvar = 'my Value' - -muss die Variable folgendermaßen definiert werden: - -.. code-block:: python - - logic.myvar = 'my Value' - - -Die Variable **logic.myvar** übersteht die Zeit bis zum nächsten Lauf der Logik und sie steht -nur in der Logik zur Verfügung, die sie auch definiert hat. - - -Existenz einer Variablen sicherstellen -====================================== - -Wenn auf eine Variable zugegriffen wird bevor sie definiert wird, führt das zu einer Exception -und der Rest der Logik wird nicht ausgeführt. Beim ersten Lauf einer Logik nach einem Neustart -von SmartHomeNG existiert jedoch keine Variable aus vorangegangenen Läufen. Sie muss erstmal -definiert werden. Das kann zum Beispiel in der folgenden Form erfolgen, in der die Variable -**logic.myvar** mit dem Wert **None** initialisiert wird, falls sie nicht existiert - -.. code-block:: python - :caption: Sicherstellen, dass die Variable existiert - - if not hasattr(logic, 'myvar'): - logic.myvar = None - - -Nutzung selbst definierter Parameter -==================================== - -Es ist möglich eigene Parameter in der Datei **../etc/logic.yaml** zu definieren. Diese Parameter -stehen in der Logik unter **logic.** zur Verfügung. Diese Parameter können als -eine bereits initialisierte Variable verstanden/genutzt werden. Sie können in der Logik nicht -nur gelesen, sondern auch verändert werden. Diese Änderung geht wie beschrieben bei einem -Neustart von SmartHomeNG verloren. - - -.. attention:: - - Einschränkungen bei Variablennamen: - - Variablennamen dürfen nicht gleich einem Namen der Attribute sein, die im Abschnitt - **Verfügbare Objekte und Methoden** beschrieben sind. - - -Einrichtung (Logik übergreifend) -================================ - -Statt eine persistende lokale Variable einzurichten: - -.. code-block:: python - - :caption: persistent, lokal zu definierenden Logik - - logic.myvar = 'my Value' - - -kann eine Variable Logik-übergreifend eingerichtet werden. Dann ist als Präfix statt *logic.* -der Präfix *logics.* zu verwenden: - -.. code-block:: python - - :caption: persistent, für alle Logiken zugreifbar - - logics.myvar = 'my Value' - - -Analog zur lokalen persistenten Variable muss die Existenz folgendermaßen sichergestellt werden: - -.. code-block:: python - :caption: Sicherstellen, dass die Variable existiert - - if not hasattr(logics, 'myvar'): - logics.myvar = None - - -Unterschiede zu lokalen persistenten Variablen ----------------------------------------------- - -Eine einmal initialisierte Logik-übergreifende persistente Variable behält ihren Wert bis -zum Neustart von SmartHomeNG. - -.. attention:: - - Da die Logik-übergreifende Variable ihren Wert auch behält, wenn die Logik die sie initialisiert hat - neu geladen wird, kann es zu unerwarteten Ergebnissen kommen, da sich die Logik nun evtl. bei einem - Neustart der Logik anders verhält, als beim Neustart von SmartHomeNG! - - - -.. index:: Funktionen; Logiken -.. index:: Logiken; Funktionen - -====================== -Nutzung von Funktionen -====================== - -Bei der Nutzung von Funktionen in Logiken ist eine Besonderheit zu beachten: - -Eine Logik verhält sich nicht wie ein Python Modul! Variablen und Funktionen die auf Ebene der Logik definiert werden, -sind keine globalen Objekte. Sie stehen in Funktionen die innerhalb der Logik definiert werden nicht zur Verfügung. -Daher müssen Variablen und Funktionen die innerhalb von Funktionen genutzt werden, der Funktion explizit bekannt gemacht +Die Logik-Skripte müssen im Verzeichnis **../logics** der SmartHomeNG Installation abgelegt werden. Damit die Logiken +getriggert und ausgeführt werden, müssen sie zusätzlich in der Konfigurationsdatei **../etc/logic.yaml** konfiguriert werden. -Dafür müssen Funktionen und Variablen der Funktion als Parameter übergeben werden. Das kann geschehen, indem die -Übergabe für jede Variable/Funktion einzeln erfolgt oder sie können in einem Objekt übergeben werden (was die zu -bevorzugende Methode ist. - -Dazu kann das Objekt **logic** genutzt werden, welches SmartHomeNG zur Verfüfung stellt um Variablen zu implementieren, -die den Lauf der Logik "überleben" und beim nächsten Lauf dieser Logik wieder zur Verfügung stehen. Das **logic** -Objekt ist privat. Das bedeutet, jede Logik hat ihr eigenes **logic** Objekt. - -Funktionen müssen dazu in der Definition den zusätzlichen Parameter **logic** enthalten. Das sollte zur besseren -Handhabung der letzte Parameter sein. Da dieser Parameter innerhalb der Logik immer mit dem selben Wert übergeben wird, -kann der Wert auch gleich als Standard-Wert in der Funktionsdefinition mit angegeben werden. Dann braucht er in den -Aufrufen der Funktion nicht angegeben zu werden. - -Das folgende Beispiel verdeutlicht das Vorgehen: - -.. code-block:: python - - # Variablen, die in Funktionen genutzt werden sollen, müssen dem logic Objekt zugewiesen werden - logic.sh = sh - logic.myvar1 = 5 - logic.myvar2 = False - - # Funktionen definieren und anschließend dem logic Objekt zuweisen - def func1(wert, logic=logic): - logger.warning("Funktion 1: wert = {}".format(wert)) - logic.func1 = func1 - - def func2(logic=logic): - logger.warning("Funktion 2") - logic.func1(2) - logic.func2 = func2 - - - # Main Routine der Logik - logic.func1(1) - logic.func2() - -Um aus Funktionen heraus auf das **sh** Objekt zugreifen zu können, sollte auch dieses (wie im obigen Beispiel) als -Variable im **logic** Objekt abgelegt werden. - - -.. index:: Logiken; Klassen - -================================ -Klassen in Logiken :redsup:`Neu` -================================ - -In Logiken können auch Klassen definiert werden. Damit diese Klassen in Funktionen zur Verfügung stehen, -muss auch hier (wie bei Funktionen) die Klasse dem Logik Objekt zugewiesen werden (letzte Zeile im folgenden Beispiel): - -.. code-block:: python - - class triggervalue(): - - def __init__(self, init_value=None, trigger_item=None, logic=None): - self.trigger_item = trigger_item - self.value = init_value - self.changed = False - self._last_value = init_value - if self.trigger_item is not None: - self.value = logic.items.return_item(trigger_item)() - self.changed = (logic.trigger_dict['source'] == trigger_item) - self._last_value = None - - def set(self, newvalue): - if self.trigger_item is None: - self._last_value = self.value - self.value = newvalue - self.changed = self.value != self._last_value - return self.changed - return self.changed - logic.triggervalue = triggervalue - -Wenn in einer Klasse auf Elemente des **logic** Objektes zugegriffen werden soll (wie in dem obigen Beispiel), -muss **logic** beim Erstellen einer Instanz mit übergeben werden: - -.. code-block:: python - - logic.freigabe_sued = logic.triggervalue(trigger_item='beschattung.beschattungsautomatik.sued', logic=logic) - +.. attention:: + **Die Informationen zu Logiken haben in der Dokumentation einen neuen Platz gefunden**: -.. index:: mqtt; Logiken -.. index:: Logiken; mqtt - -=========================== -Nutzung von MQTT in Logiken -=========================== - -Die Nutzung des MQTT Protokolls in Logiken wird durch das mqtt Modul von SmartHomeNG möglich, welches ab Version 1.7 -zur Verfügung steht. Zur Nutzung muss das mqtt Modul geladen und konfiguriert sein und der konfigurierte MQTT Broker -muss laufen. - - -Das mqtt Objekt -=============== - -Um MQTT in Logiken zu nutzen, steht ein Objekt **mqtt** zur Verfügung. Falls das mqtt Modul geladen/konfiguriert ist -oder keine Verbindung zum Broker besteht, ist **mqtt** **None**. In Logiken kann folgendermaßen geprüft werden, ob -MQTT Unterstützung besteht: - -.. code-block:: python - :caption: Test ob MQTT Unterstützung besteht - - if mqtt is None: - # no MQTT support available - logger.warning("MQTT module is not loaded or not yet initialized") - - -Das **mqtt** Objekt stellt folgende Funktionen bereit, um MQTT Messages zu versenden oder zu empfangen: - - -mqtt.publish_topic() --------------------- - -Eine MQTT Message kann versendet werden, indem die Funktion folgendermaßen aufgerufen wird: - -.. code-block:: python - :caption: publish_topic() - - mqtt.publish_topic(source_logic, topic, payload) - - -+-------------------------+------------------------------------------------------------------------------------------------------+ -| Parameter | Bemerkung | -+=========================+======================================================================================================+ -| source_logic | Name der Logik, welche die Funktion **publish_topic** aufruft. Hier kann einfach die Variable | -| | **logic.name** genutzt werden. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| topic | Topic der zu veröffentlichenden MQTT Message als **str**. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| payload | Payload der zu veröffentlichenden MQTT Message. Die Variable kann jedem Python Datentyp sein. | -+-------------------------+------------------------------------------------------------------------------------------------------+ - - -mqtt.subscribe_topic() ----------------------- - -Messages die ein bestimmtes MQTT Topic enthalten, können folgendermaßen abonniert werden: - -.. code-block:: python - :caption: subscribe_topic() - - mqtt.subscribe_topic(source_logic, topic, callback, qos=None, payload_type='str', bool_values=None) - - -+-------------------------+------------------------------------------------------------------------------------------------------+ -| Parameter | Bemerkung | -+=========================+======================================================================================================+ -| source_logic | Name der Logik, welche die Funktion **subscribe_topic** aufruft. Hier kann einfach die Variable | -| | **logic.name** genutzt werden. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| topic | Topic welches aboniert werden soll. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| callback | Name der Logik, welche bei Empfang des abonnierten Topics getriggert werden soll. Das kann die | -| | aufrufende Logik sein oder eine andere Logik. Wenn die aufrufende Logik getriggert werden soll, kann | -| | hier einfach die Variable **logic.name** genutzt werden. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| qos | **Optional**: Quality-of-Service, falls ein von der Standardeinstellung abweichender qos genutzt | -| | werden soll. Anderenfalls kann dieser Parameter weggelassen werden. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| payload_type | **Optional**: Datentyp in welchen die Payload umgewandelt werden soll. Wenn dieser Parameter nicht | -| | angegeben wird, wird der Datentyp **str** verwendet. Angegeben werden können folgende SmartHomeNG | -| | Datentypen: 'str', 'num', 'bool', 'list', 'dict', 'scene' oder 'bytes' - **bytes** bedeutet, dass | -| | keine Typenwandlung vorgenommen wird. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| bool_values | **Optional**: Legt fest, welche Werte des Payload als **bool** Values interpretiert werden sollen. | -| | Falls der Parameter angegeben wird, muss eine Liste mit **zwei** Werten angegeben werden, wobei der | -| | erste Wert für **False** steht und der zweiter Wert für **True** | -+-------------------------+------------------------------------------------------------------------------------------------------+ - -Wenn eine als **callback** angegebene Logik getriggert wird, ist das **trigger** dict mit folgenden Werten belegt: - -- **trigger['source']** - 'mqtt' - Konstante -- **trigger['by']** - - Dadurch kann bestimmt werden, wie die payload zu behandeln ist, falls eine Logik die Callbacks mehrer topics erhält -- **trigger['value']** - , wobei der Datentyp der payload dem entspricht, was in mqtt.subscribe_topic() als payload_type angegeben wurde - -Es können mehrere Logiken das selbe Topic abonnieren. Alle Logiken die das Topic abonniert haben, werden beim Eintreffen -einer passenden MQTT Message getriggert. - - -mqtt.unsubscribe_topic() ------------------------- - -Ein bestehendes Abonnement für Messages die ein bestimmtes MQTT Topic enthalten, kann folgendermaßen beendet werden: - -.. code-block:: python - :caption: unsubscribe_topic() - - mqtt.unsubscribe_topic(source_logic, topic) - - -+-------------------------+------------------------------------------------------------------------------------------------------+ -| Parameter | Bemerkung | -+=========================+======================================================================================================+ -| source_logic | Name der Logik, welche die Funktion **subscribe_topic()** für den Topic aufgerufen hatte. | -+-------------------------+------------------------------------------------------------------------------------------------------+ -| topic | Topic dessen Subscription beendet werden soll. | -+-------------------------+------------------------------------------------------------------------------------------------------+ - - - -Beispiel Logik -============== - -Hier ist eine Beispiel Logik, die sowohl Subscriptions ausführt, als auch die Callbacks behandelt: - -.. code-block:: python - :caption: Beispiel Logik **mqtt_demo** - - #!/usr/bin/env python3 - # logics/mqtt_demo.py - - def logic_publish_topic(logger, mqtt, logic, topic, payload): - logger.info("Function '{}()' - called by '{}()' in logic '{}'".format(inspect.stack()[0][3], inspect.stack()[1][3], logic.name)) - if mqtt.publish_topic(logic.name, topic, payload): - logger.info("Function '{}()' - test-topic was published".format(inspect.stack()[0][3], inspect.stack()[1][3])) - else: - logger.warning("Function '{}()' - test-topic was NOT published".format(inspect.stack()[0][3], inspect.stack()[1][3])) - - def logic_subscribe_topic(logger, mqtt, logic, topic, payload_type='str'): - logger.info("Function '{}()' - called by '{}()' in logic '{}'".format(inspect.stack()[0][3], inspect.stack()[1][3], logic.name)) - mqtt.subscribe_topic(logic.name, topic, None, payload_type, logic.name) + Weitere Informationen und Details zur Erstellung von Logiken finden sich nun in der Dokumentation + unter :doc:`Referenz/Logiken ` + und unter :doc:`Entwicklung/Logiken `. - # logic main-code starts here - logger.info("Triggered: trigger['source'] = {}, trigger[by] = {}, trigger[value] = {}".format(trigger['source'], trigger['by'], trigger['value']) ) - if mqtt is None: - # no MQTT support available - logger.warning("MQTT module is not loaded or not yet initialized") - elif trigger['source'] == 'mqtt': - # callback received - topic = trigger['by'] - payload = trigger['value'] - logger.info("MQTT received topic '{}': payload = '{}' - type(payload) = {})".format(topic, payload, type(payload))) - else: - mydict = {'txt': 'Test payload 2', 'num': 5} - # logger, mqtt and logic are handed over to functions, because only this way they are accessable in a logic's function - logic_publish_topic(logger, mqtt, logic, 'test_mqtt/topic', 'Test payload') - logic_publish_topic(logger, mqtt, logic, 'test_mqtt/topic2', mydict) - logic_subscribe_topic(logger, mqtt, logic, 'test_mqtt/sub') - logic_subscribe_topic(logger, mqtt, logic, 'test_mqtt/sub2', 'dict') +Details zur Konfiguration von Logiken finden sich nach wie vor auch unter :doc:`Konfiguration/Logiken <../konfiguration/logiken>` . -.. hint:: - Den **logger** müsste man nicht unbedingt an die Funktionen in der Logik übergeben, aber dann würden im Log die - Einträge aus Funktionen innerhalb der Logik im Logfile als Modul nicht **logics.mqtt_demo** angeben, sondern - **scheduler**. diff --git a/doc/user/source/referenz/items/standard_attribute/eval.rst b/doc/user/source/referenz/items/standard_attribute/eval.rst index 7df35d8287..67bc568b8e 100644 --- a/doc/user/source/referenz/items/standard_attribute/eval.rst +++ b/doc/user/source/referenz/items/standard_attribute/eval.rst @@ -101,8 +101,8 @@ zusätzliche Funktionen in eval Ausdrücken Bereits SmartHomeNG v1.3 wird das Python Modul `math `__ bereitgestellt und es können entsprechende Funktionen genutzt werden. Außerdem sind seit SmarthomeNG v1.7 die -:doc:`Items-API ` als **items** (z.B. items.return_item('bla')) und das -:doc:`shtime Modul ` mittels **shtime** (z.B. shtime.now()) verfügbar. +:doc:`Items-API ` als **items** (z.B. items.return_item('bla')) und das +:doc:`shtime Modul ` mittels **shtime** (z.B. shtime.now()) verfügbar. Beispiel: @@ -127,7 +127,7 @@ auf andere Items beziehen. .. tip:: - Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` + Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` beschrieben, welche Feiertags- und Datums-Funktionen in Logiken benutzt werden können. Diese Funktionen können auch in eval Attributen genutzt werden können. diff --git a/doc/user/source/referenz/logging/logging.rst b/doc/user/source/referenz/logging/logging.rst index f7d9266842..e7b850f8e8 100644 --- a/doc/user/source/referenz/logging/logging.rst +++ b/doc/user/source/referenz/logging/logging.rst @@ -6,9 +6,9 @@ .. role:: redsup -===================== -Logging :redsup:`Neu` -===================== +======= +Logging +======= SmartHomeNG nutzt das Logging-Modul von Python. In den folgenden Abschnitten sind Informationen zum Logging in SmartHomeNG zu finden. diff --git a/doc/user/source/referenz/logiken/logiken.rst b/doc/user/source/referenz/logiken/logiken.rst new file mode 100644 index 0000000000..44bb4c3b62 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken.rst @@ -0,0 +1,44 @@ +:tocdepth: 5 + +.. index:: Referenz; Logiken +.. Index:: Logiken; Referenz + +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + + +##################### +Logiken :redsup:`Neu` +##################### + +Logiken dienen dazu, komplexere Zustände und Abläufe zu steuern, die über Item Attribute nicht abgebildet werden +können. Logiken für SmartHomeNG sind Python Skripte. Zur Erstellung von Logiken müssen Sie über Kenntnisse +der Programmiersprache Python verfügen. + +In diesem Abschnitt wird die grundlegende Struktur einer Logik, sowie deren Konfiguration beschrieben. Weiterhin +werden die definierten Objekte und Methoden, sowie die zur Verfügung stehenden Pythom Module erläutert. Im weiteren +wird beschrieben, wie auf Items zugegriffen wird. + +Das mqtt Modul bietet einen Support für Logiken, der auch in diesem Abschnitt dokumentiert ist. + +Außerdem werden die Prinzipien der Nutzung von Plugin Funktionen beschrieben (falls das jeweilige Plugin Funktionen +definiert hat). + +.. toctree:: + :maxdepth: 5 + :hidden: + + logiken_grundstruktur + logiken_konfiguration + logiken_logic_objekt + objekteundmethoden + logiken_logging + logiken_python_module + logiken_smarthomeng_methoden + logiken_persistente_vars + logiken_items + logiken_funktionen + logiken_mqtt + diff --git a/doc/user/source/referenz/logiken/logiken_funktionen.rst b/doc/user/source/referenz/logiken/logiken_funktionen.rst new file mode 100644 index 0000000000..991a010d7f --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_funktionen.rst @@ -0,0 +1,104 @@ + +:tocdepth: 5 + +================================= +Funktionen und Klassen in Logiken +================================= + + +.. index:: Funktionen; Logiken +.. index:: Logiken; Funktionen + +Funktionen in Logiken +===================== + +Bei der Nutzung von Funktionen in Logiken ist eine Besonderheit zu beachten: + +Eine Logik verhält sich nicht wie ein Python Modul! Variablen und Funktionen die auf Ebene der Logik definiert werden, +sind keine globalen Objekte. Sie stehen in Funktionen die innerhalb der Logik definiert werden nicht zur Verfügung. +Daher müssen Variablen und Funktionen die innerhalb von Funktionen genutzt werden, der Funktion explizit bekannt gemacht +werden. + +Dafür müssen Funktionen und Variablen der Funktion als Parameter übergeben werden. Das kann geschehen, indem die +Übergabe für jede Variable/Funktion einzeln erfolgt oder sie können in einem Objekt übergeben werden (was die zu +bevorzugende Methode ist. + +Dazu kann das Objekt **logic** genutzt werden, welches SmartHomeNG zur Verfüfung stellt um Variablen zu implementieren, +die den Lauf der Logik "überleben" und beim nächsten Lauf dieser Logik wieder zur Verfügung stehen. Das **logic** +Objekt ist privat. Das bedeutet, jede Logik hat ihr eigenes **logic** Objekt. + +Funktionen müssen dazu in der Definition den zusätzlichen Parameter **logic** enthalten. Das sollte zur besseren +Handhabung der letzte Parameter sein. Da dieser Parameter innerhalb der Logik immer mit dem selben Wert übergeben wird, +kann der Wert auch gleich als Standard-Wert in der Funktionsdefinition mit angegeben werden. Dann braucht er in den +Aufrufen der Funktion nicht angegeben zu werden. + +Das folgende Beispiel verdeutlicht das Vorgehen: + +.. code-block:: python + + # Variablen, die in Funktionen genutzt werden sollen, müssen dem logic Objekt zugewiesen werden + logic.sh = sh + logic.myvar1 = 5 + logic.myvar2 = False + + # Funktionen definieren und anschließend dem logic Objekt zuweisen + def func1(wert, logic=logic): + logger.warning("Funktion 1: wert = {}".format(wert)) + logic.func1 = func1 + + def func2(logic=logic): + logger.warning("Funktion 2") + logic.func1(2) + logic.func2 = func2 + + + # Main Routine der Logik + logic.func1(1) + logic.func2() + +Um aus Funktionen heraus auf das **sh** Objekt zugreifen zu können, sollte auch dieses (wie im obigen Beispiel) als +Variable im **logic** Objekt abgelegt werden. + + +.. index:: Klassen; Logiken +.. index:: Logiken; Klassen + +Klassen in Logiken +================== + +In Logiken können auch Klassen definiert werden. Damit diese Klassen in Funktionen zur Verfügung stehen, +muss auch hier (wie bei Funktionen) die Klasse dem Logik Objekt zugewiesen werden (letzte Zeile im folgenden Beispiel): + +.. code-block:: python + + class triggervalue(): + + def __init__(self, init_value=None, trigger_item=None, logic=None): + self.trigger_item = trigger_item + self.value = init_value + self.changed = False + self._last_value = init_value + if self.trigger_item is not None: + self.value = logic.items.return_item(trigger_item)() + self.changed = (logic.trigger_dict['source'] == trigger_item) + self._last_value = None + + def set(self, newvalue): + if self.trigger_item is None: + self._last_value = self.value + self.value = newvalue + self.changed = self.value != self._last_value + return self.changed + return self.changed + logic.triggervalue = triggervalue + +Wenn in einer Klasse auf Elemente des **logic** Objektes zugegriffen werden soll (wie in dem obigen Beispiel), +muss **logic** beim Erstellen einer Instanz mit übergeben werden: + +.. code-block:: python + + logic.freigabe_sued = logic.triggervalue(trigger_item='beschattung.beschattungsautomatik.sued', logic=logic) + + + + diff --git a/doc/user/source/referenz/logiken/logiken_grundstruktur.rst b/doc/user/source/referenz/logiken/logiken_grundstruktur.rst new file mode 100644 index 0000000000..9a099a0076 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_grundstruktur.rst @@ -0,0 +1,43 @@ + +:tocdepth: 5 + +.. index:: Struktur; Logiken +.. index:: Logiken; Struktur + +========================= +Grundstruktur einer Logik +========================= + +Das eigentliche Python Skript einer Logik muss im Verzeichnis ``../logics`` der SmartHomeNG Installation abgelegt +werden. Damit die Logiken getriggert und ausgeführt werden, müssen sie zusätzlich in der Konfigurationsdatei +``../etc/logic.yaml`` konfiguriert werden. + +> - - - - - - - - - - + +Das wichtigste Objekt einer Logik ist das Smarthome Objekt ``sh``. +Über dieses Objekt kann auf alle Items, Plugins und Funktionen von SmartHomeNG +zugegriffen werden. +Um den Wert eines Items abzufragen kann zum Beispiel ``sh.area.itemname()`` verwendet werden. +Um dem gleichen Item einen neuen Wert mitzugeben, kann dieser Wert als Argument +übergeben werden ``sh.area.itemname(new\_value)`` + +.. code-block:: python + + #!/usr/bin/env python + # put on the light in the living room, if it is not on + if not sh.living_room.light(): + sh.living_room.light('on') + +Auf Items muß unter Nutzung von Klammern ``()`` zugegridden werden sonst wird eine Ausnahme +erzeugt. + +``sh`` kann genutzt werden um über alle Items durchzulaufen: + +.. code-block:: python + + for item in sh: + print item + for child_item in item: + print child_item + + diff --git a/doc/user/source/referenz/logiken/logiken_items.rst b/doc/user/source/referenz/logiken/logiken_items.rst new file mode 100644 index 0000000000..24e3d5c4ef --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_items.rst @@ -0,0 +1,136 @@ +:tocdepth: 5 + +.. index:: Zugriff auf Items; Logiken +.. index:: Logiken; Zugriff auf Items + + +================= +Zugriff auf Items +================= + +.. index:: Item Wert; Logiken +.. index:: Logiken; Item Wert + +Zugriff auf den Wert eines Items +================================ + +Um Zugriff auf Items zu erhalten, müssen Sie über die zentrale Instanz **sh** des Smarthome Objektes angesprochen +werden. Um zum Beispiel den Wert des Items **path.item** zu erhalten, muss der Aufruf **sh.path.item()** lauten. +Der Wert wird über eine Methode des Items aufgerufen, weshalb der Name des Item Objektes um **()** ergänzt werden +muss. Um dem Item einen neuen Wert zuzuweisen, wird dieser einfach als Argument angegeben: **sh.path.item(neuer_wert)**. + +.. attention:: + + Zuweisung von Item-Werten: + + Es ist sehr wichtig, immer mit Klammern **()** auf die Items zuzugreifen! Wenn (fehlerhafter weise) das Item direkt + zugewiesen wird, z.B. mit **sh.path.item = Wert**, dann wird das item-Objekt in SmartHomeNG überschrieben. + Anschließend ist **sh.path.item** eine normale Variable und kein Item Objekt mehr. + + In diesem Fall kann die mitgelieferte Logik **check_items.py** verwendet werden, um auf Vorhandensein + entsprechend beschädigter Items zu prüfen und diese wiederherzustellen. Alternativ werden die Items nach + einem Neustart von SmartHomeNG neu erstellt. + + +Alternativ kann auch über die Item-Properties auf den Wert zugegriffen werden: **sh.path.item.propery.value** +gibt den Wert zurück, mit **sh.path.item.property.value = Wert** kann der Wert zugewiesen werden. Diese Variante +lässt sich wie eine normale Variablenzuweisung nutzen. + +Beispiel +======== + +Eine Logik sieht prinzipiell folgendermaßen aus: + +.. code-block:: python + :caption: /usr/local/smarthome/logics/testlogik1.py + + #!/usr/bin/env python3 + # testlogik1.py + + #Code der Logik: + + # Das Deckenlicht im Büro einschalten, falls es nicht eingeschaltet ist + if not sh.buero.deckenlicht(): + sh.buero.deckenlicht('on') + + + +weiteres +======== + +Methoden zum Zugriff auf Items +------------------------------ + +Die Nutzung des ``sh`` Objektes für Items wird nicht weitergeführt. Es ist besser das Item API wie folgt zu nutzen: + +.. code:: python + + from lib.item import Items + items = Items.get_instance() + +Mit dem ``items`` Objekt können nun die folgenden Funktionen verwendet werden: + + +items.return_item(path) +~~~~~~~~~~~~~~~~~~~~~~~ + +Liefert ein Item Objekt für den angegebenen Pfad zurück. Beispiel: + +``items.return_item('first_floor.bath')`` + +items.return_items() +~~~~~~~~~~~~~~~~~~~~ + +Liefert alle Item Objekte zurück + +.. code-block:: python + + for item in items.return_items(): + logger.info(item.id()) + +items.match_items(regex) +~~~~~~~~~~~~~~~~~~~~~~~~ + +Liefert alle Item Objekte deren Pfad mit einem regulären Ausdruck gefunden wird und die optional ein bestimmtes Attribut aufweisen. + +.. code-block:: python + + for item in items.match_items('*.lights'): # selects all items ending with 'lights' + logger.info(item.id()) + + for item in items.match_items('*.lights:special'): # selects all items ending with 'lights' and attribute 'special' + logger.info(item.id()) + +items.find_items(configattribute) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Abhängig von ``configattribute`` werden die folgenden Items zurückgegeben: + +.. table:: + + ====================== ========================================================= + Attribut Ergebnis + ====================== ========================================================= + ``attribute`` Nur Items bei denen keine Instanz ID angegeben ist + ``attribute@`` Items mit oder ohne Instanz ID + ``attribute@instance`` Items mit einem bestimmten Attribut und einer Instanz ID + ``@instance`` Items mit einer bestimmten Instanz ID + ====================== ========================================================= + + +.. code:: python + + for item in items.find_items('my_special_attribute'): + logger.info(item.id()) + +find\_children(parentitem, configattribute): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Liefert alle Kind Item Objekte eines Elternitems mit einem gegebenen ``configattribute``. +Die Suche nach dem ``configattribute`` wird genauso durchgeführt wie in ``find_items(configattribute)`` weiter oben. + +... + +- Methoden +- Item Werte und Attribute + diff --git a/doc/user/source/referenz/logiken/logiken_konfiguration.rst b/doc/user/source/referenz/logiken/logiken_konfiguration.rst new file mode 100644 index 0000000000..4c31110866 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_konfiguration.rst @@ -0,0 +1,231 @@ +:tocdepth: 5 + +.. role:: bluesup + +.. index:: Konfiguration; Logiken +.. index:: Logiken; Konfiguration + + +=================== +Logik Konfiguration +=================== + +Details zur Konfiguration von Logiken finden sich :doc:`hier <../../konfiguration/logiken>` . + + +Zur Konfiguration einer Logik wird in der Datei **../etc/logic.yaml** ein Abschnitt für die +Logik angelegt. Unter dem Namen dieses Abschnitts wird die Logik an anderen Stellen referenziert. + +In diesem Abschnitt muss SmartHomeNG mitgeteilt werden, welche Code Datei ausgeführt werden soll +und unter welchen Umständen. Dazu werden die im Folgenden beschriebenen Parameter genutzt. + + +.. include:: logiken_standardparameter.rst + + +Die folgenden Parameter können genutzt werden um eine Logik und ihre Ausführungsumstände festzulegen: + +.. _logik_parameter_watch_item: + +watch_item +~~~~~~~~~~ + +Die Liste der angegebenen Items wird auf Änderungen überwacht + +.. code-block:: yaml + :caption: etc/logic.yaml + + logicnamehere: + watch_item: + - house.alarm + - garage.alarm + + +Jede Änderung bei den Items **house.alarm** oder **garage.alarm** löst die Ausführung der angegebenen Logik aus. +Es ist möglich einen Stern * für einen Pfadbestandteil zu setzen, ähnlich eines regulären Ausdrucks: + +.. code-block:: yaml + + watch_item: '*.door' + +Eine Änderung von **garage.door** oder auch **house.door** wird die Ausführung der Logik auslösen aber **nicht** +eine Änderung von **house.hallway.door** + + +.. _logik_parameter_cycle: + +cycle +~~~~~ + +Sorgt für eine zyklische Ausführung der Logik + +.. code-block:: yaml + + cycle: 60 + + +Optional kann ein Argument übergeben werden + +.. code-block:: yaml + + cycle: 60 = 100 + + +Dadurch wird die Logik alle 60 Sekunden ausgeführt und der Wert 100 an die Logik übergeben. +Innerhalb der Logik kann auf den Wert über ``trigger['value']`` zugegriffen werden + + +.. _logik_parameter_crontab: + +crontab :bluesup:`Update` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. Der Inhalt der Beschreibung von crontab wurde aus referenz/items/standard_attribute/crontab.rst 1:1 kopiert + +Es gibt drei verschiedene Parametersätze für ein Crontab Attribut: + +.. tabs:: + + .. tab:: init + Das Item wird zum Start von SmarthomeNG aktualisiert und triggert + dadurch unter Umständen eine zugewiesene Logik: + + .. code-block:: yaml + + crontab: init + + Hier kann auch zusätzlich ein Offset angegeben werden um den + tatsächlichen Zeitpunkt zu verschieben: + + .. code-block:: yaml + + crontab: init+10 # 10 Sekunden nach Start + + .. tab:: Zeitpunkte + + Das Item soll zu bestimmten Zeitpunkten aktualisiert werden. + Die Schreibweise ist an Linux Crontab angelehnt, entspricht diesem aber nicht genau. + Es gibt je nach Parameteranzahl 3 Varianten: + + * ``crontab: `` + * ``crontab: `` + * ``crontab: `` + + Dabei sind je nach Variante folgende Werte zulässig: + + * Sekunde: ``0`` bis ``59`` + * Minute: ``0`` bis ``59`` + * Stunde: ``0`` bis ``23`` + * Tag: ``1`` bis ``31`` + * Monat: 1 bis 12 oder ``jan`` bis ``dec`` + * Wochentag ``0`` bis ``6`` oder ``mon``, ``tue``, ``wed``, ``thu``, ``fri``, ``sat``, ``sun`` + + Alle Parameter müssen durch ein Leerzeichen getrennt sein und innerhalb eines Parameters + darf kein zusätzliches Leerzeichen vorhanden sein, sonst kann der Parametersatz nicht ausgewertet werden. + + Im folgenden Beispiel wird jeden Tag um 23:59 ein Trigger erzeugt und der Wert 70 gesetzt. + + .. code-block:: yaml + + crontab: 59 23 * * = 70 + + Für jede dieser Zeiteinheiten (Minuten, Stunde, Tag, Wochentag) werden + folgende Muster unterstützt (Beispiel jeweils ohne Anführungszeichen verwenden): + + * eine einzelne Zahl, z.B. ``8`` → immer zur/zum 8. Sekunde/Minute/Stunde/Tag/Wochentag + * eine Liste von Zahlen, z.B. ``2,8,16`` → immer zur/zum 2., 8. und 16. Sekunde/Minute/Stunde/Tag/Monat/Wochentag + * ein Wertebereich, z.B. ``1-5`` → immer zwischen dem/der 1. und 5. Sekunde/Minute/Stunde/Tag/Monat/Wochentag + * einen Interval, z.B. ``\*\/4`` → immer alle 4 Sekunden/Minuten/Stunden/Tage/Wochentage + * einen Stern, z.B. ``*`` → jede Sekunde/Minute/Stunde/Tag/Monat/Wochentag + + .. tab:: Zeitpunkte bezogen auf Aufgang von Sonne oder Mond + + Nach dem Muster ``[H:M<](sunrise|sunset|moonrise|moonset)[+|-][offset][ )`` kann ein Triggerpunkt bezogen + auf Sonne oder Mond berechnet werden: + + * ``sunrise`` → immer zum Sonnenaufgang + * ``sunset`` → immer zum Sonnenuntergang + * ``sunrise`` und untere Begrenzung → ``06:00` +und :doc:`Entwicklung/Logiken `. \ No newline at end of file diff --git a/doc/user/source/referenz/logiken/logiken_logging.rst b/doc/user/source/referenz/logiken/logiken_logging.rst new file mode 100644 index 0000000000..acd8dcf4d9 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_logging.rst @@ -0,0 +1,56 @@ +:tocdepth: 5 + +.. index:: Logging; Logging in Logiken +.. index:: Logiken; Logging in Logiken + + +================== +Logging in Logiken +================== + +Damit aus Logiken heraus geloggt werden kann, muss dafür in der Logging-Konfigurationsdatei ``etc/logging.yaml`` +ein entsprechender Logger konfiguriert sein. + +Es sollte auf jeden Fall ein Standardlogger für alle Logiken konfiguriert sein, damit Warnungen und Fehler geloggt +werden können. Dazu ist in der Standardkonfiguration von SmartHomeNG der notwendige Logger bereits folgendermaßen +konfiguriert: + +.. code-block:: yaml + :caption: etc/logging.yaml + + logger: + + ... + + logics: + handlers: [shng_details_file] + level: WARNING + +Wenn aus allen Logiken heraus auch INFOs und DEBUG Informationen geloggt werden sollen, kann hier der ``level`` +entsprechend angepasst werden. + +Ein besserer Weg ist es jedoch, für die jeweilige Logik aus der heraus weitere Informationen geloggt werden sollen, +einen eigenen Logger anzulegen. Dadurch kann das Logging für jede Logik unabhängig von den anderen Logiken festgelegt +werden. Dazu muss ein weiterer Logger angelegt werden, der den Namen der Logik enthält ``logics.``. +Dabei ist der Logikname **nicht** der Name der Python Skript Datei, sondern der Name des Abschnitts in der +Konfigurationsdatei ``etc/logics.yaml``. + +Für eine Logik mit dem Namen ``example``, sieht das beispielsweise folgendermaßen aus: + +.. code-block:: yaml + :caption: etc/logging.yaml + + logger: + + ... + + logics: + handlers: [shng_details_file] + level: WARNING + + logics.example: + level: INFO + +Als Handler wird dabei der bereits im Logger ``logics`` definierte Handler verwendet. Es können bei Bedarf im +Logger der einzelnen Logik zusätzliche handler angegeben werden. Dabei muss darauf geachtet werden, dass der im +Logger ``logics`` definierte Handler nicht erneut angegeben wird, sa sonst die Logausgaben doppelt erfolgen. diff --git a/doc/user/source/referenz/logiken/logiken_logic_objekt.rst b/doc/user/source/referenz/logiken/logiken_logic_objekt.rst new file mode 100644 index 0000000000..63b8011996 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_logic_objekt.rst @@ -0,0 +1,139 @@ + +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + +==================================== +Die Objekte **logic** und **logics** +==================================== + +.. index:: logic Objekt; Logiken +.. index:: Logiken; logic Objekt + +Das logic Objekt +================ + +Dieses Objekt bietet Zugriff auf das aktuelle Logikobjekt. Es ist möglich, während der Laufzeit +logische Attribute (crontab, cycle, ...) abzufragen und ändern. Diese Änderungen gehen nach dem Neustart +von SmartHomeNG verloren. + +Definierte Methoden des Logikobjekts: + ++-------------------+--------------------------------------------------------------------------------------------------------+ +| Methode | Erläuterung | ++===================+========================================================================================================+ +| logic.id() | Diese Methode liefert dem Namen der Logik wie in **../etc/logic.yaml** angegeben. | ++-------------------+--------------------------------------------------------------------------------------------------------+ +| logic.last_run() | Diese Methode liefert den letzten Lauf dieser Logik (vor aktuellen Lauf). | ++-------------------+--------------------------------------------------------------------------------------------------------+ +| logic.disable() | Konfigurierte Logiken sind standardmäßig aktiv und werden entsprechend der Konfiguration ausgeführt. | +| | Diese Methode deaktiviert die Logik, sodass deren Ausführung unterbunden wird. (Ab SmartHomeNG v1.3) | ++-------------------+--------------------------------------------------------------------------------------------------------+ +| logic.enable() | Eine bereits deaktivierte Logik kann mit dieser Methode wieder aktiviert werden. (Ab SmartHomeNG v1.3) | ++-------------------+--------------------------------------------------------------------------------------------------------+ + + +Vordefinierte Attribute des Logikobjekts: + ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| Attribut | Erläuterung | ++===========================+========================================================================================================+ +| trigger[] | Ein Python-Dictionary, welches im Folgenden beschreiben wird. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| logic.name | Das Attribut logic.name liefert das selbe Ergebnis wie die Methode logic.id() | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| logic.crontab | Das Attribut liefert das aktuelle **crontab** Setting dieser Logik. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| logic.cycle | Das Attribut liefert das aktuelle **cycle** Setting dieser Logik. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| logic.prio | Das Attribut liefert das aktuelle **prio** Setting dieser Logik. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| logic.filename | Das Attribut liefert den Dateinamen des Python Skripts dieser Logik. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| logic. | Liefert den konfigurierten Parameter oder den Wert einer in einem vorherigen Lauf dieser | +| | Logik persistieren Variablen. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ + +> - - - - - + +Dieses Objekt bietet Zugriff auf das aktuelle Logik Objekt. +Es ist möglich die Attribute wie ``crontab``, ``cycle``, etc. während der Laufzeit zu ändern. +Die Änderungen werden aber nicht in die ``logic.yaml`` geschrieben und sind nach einem +Neustart von SmartHomeNG verloren. + +``logic.alive`` + Der Code ``while logic.alive:`` erzeugt eine Endlos-Schleife die bis zum Beenden + von SmartHomeNG läuft. + +``logic.name``: + Liefert den Namen der Logik wie in ``logic.yaml`` definiert + +``logic.last_time()``: + Diese Funktion liefert die letzte Ausführungszeit der Logik vor dem aktuellen Aufruf + +``logic.prio``: + Lesen und setzen der Priorität dieser Logik + +``logic.trigger()``: + Wie ``sh.trigger()``, aber triggert nur die aktuelle Logik. Diese Funktion ist nützlich + um die Logik zu einem späteren Zeitpunkt (noch einmal) auszuführen + +> - - - - - + +.. index:: trigger dict; Logiken +.. index:: Logiken; trigger dict + +Das **trigger** Dictionary +-------------------------- + +Das **trigger** dict ist ein Python-Dictionary, welches als Laufzeitumgebung einige Informationen über das +Ereignis liefert, das die Logik ausgelöst hat. + +Das Dictionary enthält folgende Informationen: + ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| Attribut/Funktion | Erläuterung | ++===========================+========================================================================================================+ +| trigger['by'] | Auslöser ('Scheduler', Item', etc.) | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| trigger['source'] | enthält den Pfad des Items, welches die Logik getriggert hat. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| trigger['source_details'] | Falls eine Logik aus einem Item heraus getriggert wurde (also trigger['by'] == Item ist), enthält | +| | trigger['source_details'] weitere Details zum Auslöser (Beispiel: 'knx:1.1.241:ga=3/3/5') | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| trigger['dest'] | | ++---------------------------+--------------------------------------------------------------------------------------------------------+ +| trigger['value'] | enthält den Wert des Items, dass die Logik getriggert hat. | ++---------------------------+--------------------------------------------------------------------------------------------------------+ + + +.. index:: logics Objekt; Logiken +.. index:: Logiken; logics Objekt + +Das logics Objekt +================= + +Zugriff auf das Logics-API über das logics Objekt: + ++---------------------------------+---------------------------------------------------------------------------------------------------------+ +| Methode | Erläuterung | ++=================================+=========================================================================================================+ +| logics. | ermöglicht den Zugriff auf das Logics API, welches in der Developer Dokumentation beschrieben ist. | +| | Im folgenden sind einige Beispiele aufgeführt: | ++---------------------------------+---------------------------------------------------------------------------------------------------------+ +| logics.scheduler_add() | Hinzufügen eines Scheduler Eintrages für den logics-Namensraum. Der Syntax entspricht der | +| | scheduler.add() Methode. | ++---------------------------------+---------------------------------------------------------------------------------------------------------+ +| logics.scheduler_change() | Ändern eines Scheduler Eintrages im logics-Namensraum. Der Syntax entspricht der scheduler.change() | +| | Methode. | ++---------------------------------+---------------------------------------------------------------------------------------------------------+ +| logics.scheduler_remove() | Löschen eines Scheduler Eintrages im logics-Namensraum. Der Syntax entspricht der scheduler_remove() | ++---------------------------------+---------------------------------------------------------------------------------------------------------+ +| logics.trigger_logic() | Triggern einer im Logik | ++---------------------------------+---------------------------------------------------------------------------------------------------------+ +| logics.set_config_section_key() | Setzt den Wert eines Schlüssels für eine angegebene Logik (Abschnitt) permanent in ../etc/logic.yaml | ++---------------------------------+---------------------------------------------------------------------------------------------------------+ + +Der vollständige Syntax der Methoden kann der `Entwickler Dokumentation `_ entnommen werden. + diff --git a/doc/user/source/referenz/logiken/logiken_mqtt.rst b/doc/user/source/referenz/logiken/logiken_mqtt.rst new file mode 100644 index 0000000000..2d9640a4f7 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_mqtt.rst @@ -0,0 +1,171 @@ + +.. index:: mqtt; Logiken +.. index:: Logiken; mqtt + +=========================== +Nutzung von MQTT in Logiken +=========================== + +Die Nutzung des MQTT Protokolls in Logiken wird durch das mqtt Modul von SmartHomeNG möglich, welches ab Version 1.7 +zur Verfügung steht. Zur Nutzung muss das mqtt Modul geladen und konfiguriert sein und der konfigurierte MQTT Broker +muss laufen. + + +Das mqtt Objekt +=============== + +Um MQTT in Logiken zu nutzen, steht ein Objekt **mqtt** zur Verfügung. Falls das mqtt Modul geladen/konfiguriert ist +oder keine Verbindung zum Broker besteht, ist **mqtt** **None**. In Logiken kann folgendermaßen geprüft werden, ob +MQTT Unterstützung besteht: + +.. code-block:: python + :caption: Test ob MQTT Unterstützung besteht + + if mqtt is None: + # no MQTT support available + logger.warning("MQTT module is not loaded or not yet initialized") + + +Das **mqtt** Objekt stellt eine Reihe von Methoden bereit, um MQTT Messages zu versenden oder zu empfangen. +Aus diese Methoden kann natürlich nur zugegriffen werden, wenn es das mqtt Objet gibt (also mqtt nicht None ist). +Die definierten Methoden sind folgende: + + +mqtt.publish_topic() +-------------------- + +Eine MQTT Message kann versendet werden, indem die Funktion folgendermaßen aufgerufen wird: + +.. code-block:: python + :caption: publish_topic() + + mqtt.publish_topic(source_logic, topic, payload) + + ++-------------------------+------------------------------------------------------------------------------------------------------+ +| Parameter | Bemerkung | ++=========================+======================================================================================================+ +| source_logic | Name der Logik, welche die Funktion **publish_topic** aufruft. Hier kann einfach die Variable | +| | **logic.name** genutzt werden. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| topic | Topic der zu veröffentlichenden MQTT Message als **str**. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| payload | Payload der zu veröffentlichenden MQTT Message. Die Variable kann jedem Python Datentyp sein. | ++-------------------------+------------------------------------------------------------------------------------------------------+ + + +mqtt.subscribe_topic() +---------------------- + +Messages die ein bestimmtes MQTT Topic enthalten, können folgendermaßen abonniert werden: + +.. code-block:: python + :caption: subscribe_topic() + + mqtt.subscribe_topic(source_logic, topic, callback, qos=None, payload_type='str', bool_values=None) + + ++-------------------------+------------------------------------------------------------------------------------------------------+ +| Parameter | Bemerkung | ++=========================+======================================================================================================+ +| source_logic | Name der Logik, welche die Funktion **subscribe_topic** aufruft. Hier kann einfach die Variable | +| | **logic.name** genutzt werden. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| topic | Topic welches aboniert werden soll. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| callback | Name der Logik, welche bei Empfang des abonnierten Topics getriggert werden soll. Das kann die | +| | aufrufende Logik sein oder eine andere Logik. Wenn die aufrufende Logik getriggert werden soll, kann | +| | hier einfach die Variable **logic.name** genutzt werden. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| qos | **Optional**: Quality-of-Service, falls ein von der Standardeinstellung abweichender qos genutzt | +| | werden soll. Anderenfalls kann dieser Parameter weggelassen werden. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| payload_type | **Optional**: Datentyp in welchen die Payload umgewandelt werden soll. Wenn dieser Parameter nicht | +| | angegeben wird, wird der Datentyp **str** verwendet. Angegeben werden können folgende SmartHomeNG | +| | Datentypen: 'str', 'num', 'bool', 'list', 'dict', 'scene' oder 'bytes' - **bytes** bedeutet, dass | +| | keine Typenwandlung vorgenommen wird. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| bool_values | **Optional**: Legt fest, welche Werte des Payload als **bool** Values interpretiert werden sollen. | +| | Falls der Parameter angegeben wird, muss eine Liste mit **zwei** Werten angegeben werden, wobei der | +| | erste Wert für **False** steht und der zweiter Wert für **True** | ++-------------------------+------------------------------------------------------------------------------------------------------+ + +Wenn eine als **callback** angegebene Logik getriggert wird, ist das **trigger** dict mit folgenden Werten belegt: + +- **trigger['source']** - 'mqtt' - Konstante +- **trigger['by']** - - Dadurch kann bestimmt werden, wie die payload zu behandeln ist, falls eine Logik die Callbacks mehrer topics erhält +- **trigger['value']** - , wobei der Datentyp der payload dem entspricht, was in mqtt.subscribe_topic() als payload_type angegeben wurde + +Es können mehrere Logiken das selbe Topic abonnieren. Alle Logiken die das Topic abonniert haben, werden beim Eintreffen +einer passenden MQTT Message getriggert. + + +mqtt.unsubscribe_topic() +------------------------ + +Ein bestehendes Abonnement für Messages die ein bestimmtes MQTT Topic enthalten, kann folgendermaßen beendet werden: + +.. code-block:: python + :caption: unsubscribe_topic() + + mqtt.unsubscribe_topic(source_logic, topic) + + ++-------------------------+------------------------------------------------------------------------------------------------------+ +| Parameter | Bemerkung | ++=========================+======================================================================================================+ +| source_logic | Name der Logik, welche die Funktion **subscribe_topic()** für den Topic aufgerufen hatte. | ++-------------------------+------------------------------------------------------------------------------------------------------+ +| topic | Topic dessen Subscription beendet werden soll. | ++-------------------------+------------------------------------------------------------------------------------------------------+ + + + +Beispiel Logik +============== + +Hier ist eine Beispiel Logik, die sowohl Subscriptions ausführt, als auch die Callbacks behandelt: + +.. code-block:: python + :caption: Beispiel Logik **mqtt_demo** + + #!/usr/bin/env python3 + # logics/mqtt_demo.py + + def logic_publish_topic(logger, mqtt, logic, topic, payload): + logger.info("Function '{}()' - called by '{}()' in logic '{}'".format(inspect.stack()[0][3], inspect.stack()[1][3], logic.name)) + if mqtt.publish_topic(logic.name, topic, payload): + logger.info("Function '{}()' - test-topic was published".format(inspect.stack()[0][3], inspect.stack()[1][3])) + else: + logger.warning("Function '{}()' - test-topic was NOT published".format(inspect.stack()[0][3], inspect.stack()[1][3])) + + def logic_subscribe_topic(logger, mqtt, logic, topic, payload_type='str'): + logger.info("Function '{}()' - called by '{}()' in logic '{}'".format(inspect.stack()[0][3], inspect.stack()[1][3], logic.name)) + mqtt.subscribe_topic(logic.name, topic, None, payload_type, logic.name) + + + # logic main-code starts here + logger.info("Triggered: trigger['source'] = {}, trigger[by] = {}, trigger[value] = {}".format(trigger['source'], trigger['by'], trigger['value']) ) + if mqtt is None: + # no MQTT support available + logger.warning("MQTT module is not loaded or not yet initialized") + elif trigger['source'] == 'mqtt': + # callback received + topic = trigger['by'] + payload = trigger['value'] + logger.info("MQTT received topic '{}': payload = '{}' - type(payload) = {})".format(topic, payload, type(payload))) + else: + mydict = {'txt': 'Test payload 2', 'num': 5} + # logger, mqtt and logic are handed over to functions, because only this way they are accessable in a logic's function + logic_publish_topic(logger, mqtt, logic, 'test_mqtt/topic', 'Test payload') + logic_publish_topic(logger, mqtt, logic, 'test_mqtt/topic2', mydict) + logic_subscribe_topic(logger, mqtt, logic, 'test_mqtt/sub') + logic_subscribe_topic(logger, mqtt, logic, 'test_mqtt/sub2', 'dict') + + +.. hint:: + + Den **logger** müsste man nicht unbedingt an die Funktionen in der Logik übergeben, aber dann würden im Log die + Einträge aus Funktionen innerhalb der Logik im Logfile als Modul nicht **logics.mqtt_demo** angeben, sondern + **scheduler**. + diff --git a/doc/user/source/referenz/logiken/logiken_persistente_vars.rst b/doc/user/source/referenz/logiken/logiken_persistente_vars.rst new file mode 100644 index 0000000000..808ad8413a --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_persistente_vars.rst @@ -0,0 +1,140 @@ + +.. index:: Persistente Variablen; Logiken +.. index:: Logiken; Persistente Variablen + +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + +===================== +Persistente Variablen +===================== + +Normale Variablen innerhalb von Logiken sind nur für den jeweiligen Lauf gültig. Es ist jedoch +in einigen Fällen notwendig, Werte zwischen verschiedenen Läufen einer Logik zu übergeben. + +Solche persistente Variablen sind in Logiken von SmartHomeNG möglich, es sind jedoch einige +Dinge zu beachten: + +- Diese Variablen sind über mehrere Läufe von Logiken persistent. Allerdings gilt das nur während + des Laufs von SmartHomeNG. Bei einem Neustart von SmartHomeNG gehen diese Werte verloren. + Der Wert einer solchen Variablen geht auch verloren, wenn die Logik über das Backend während + der Laufzeit von SmartHomeNG gespeichert und neu geladen wird. +- Beim 1. Lauf einer Logik nach dem Start von SmartHomeNG existieren diese Variablen nicht. Der + erste Zugriff innerhalb einer Logik muss deshalb in einen **if not hasattr():** Ausdruck + eingebunden werden. +- Diese Variablen sind **lokal zur Logik**. Sie stehen außerhalb der Logik, die sie definiert hat, + nicht zur Verfügung. Wenn die Daten auch außerhalb der Logik verwendet werden, müssen sie in + Items abgelegt werden. (Ein Sonderfall sind Werte, die zwar außerhalb der definierenden Logik + verwendet werden sollen, aber nur in Logiken. Hier gibt es eine weitere Möglichkeit, die + weiter unten beschrieben wird). +- Es dürfen einige Namen nicht verwendet werden, da sie interne Variablen des Logik Objektes + überschreiben würden. + +Die Persistenz dieser Variablen gilt nur während des Laufs von SmartHomeNG. Mit der Beendigung von +SmartHomeNG gehen die gespeicherten Werte verloren. + + +Einrichtung +=========== + +Normale Variablen sind lokal zum Lauf der der Logik. Eine Variable **myvar** die während eines +Laufes der Logik einen Wert zugewiesen bekommt, ist beim Beginn des nächsten Laufes nicht +definiert. + +Um eine Variable so zu definieren, dass sie die Zeit bis zum nächsten Lauf der Logik überdauert, +muss sie als Attribut zur Logik definiert werden. Also statt: + +.. code-block:: python + + myvar = 'my Value' + +muss die Variable folgendermaßen definiert werden: + +.. code-block:: python + + logic.myvar = 'my Value' + + +Die Variable **logic.myvar** übersteht die Zeit bis zum nächsten Lauf der Logik und sie steht +nur in der Logik zur Verfügung, die sie auch definiert hat. + + +Existenz einer Variablen sicherstellen +====================================== + +Wenn auf eine Variable zugegriffen wird bevor sie definiert wird, führt das zu einer Exception +und der Rest der Logik wird nicht ausgeführt. Beim ersten Lauf einer Logik nach einem Neustart +von SmartHomeNG existiert jedoch keine Variable aus vorangegangenen Läufen. Sie muss erstmal +definiert werden. Das kann zum Beispiel in der folgenden Form erfolgen, in der die Variable +**logic.myvar** mit dem Wert **None** initialisiert wird, falls sie nicht existiert + +.. code-block:: python + :caption: Sicherstellen, dass die Variable existiert + + if not hasattr(logic, 'myvar'): + logic.myvar = None + + +Nutzung selbst definierter Parameter +==================================== + +Es ist möglich eigene Parameter in der Datei **../etc/logic.yaml** zu definieren. Diese Parameter +stehen in der Logik unter **logic.** zur Verfügung. Diese Parameter können als +eine bereits initialisierte Variable verstanden/genutzt werden. Sie können in der Logik nicht +nur gelesen, sondern auch verändert werden. Diese Änderung geht wie beschrieben bei einem +Neustart von SmartHomeNG verloren. + + +.. attention:: + + Einschränkungen bei Variablennamen: + + Variablennamen dürfen nicht gleich einem Namen der Attribute sein, die im Abschnitt + **Verfügbare Objekte und Methoden** beschrieben sind. + + +Einrichtung (Logik übergreifend) +================================ + +Statt eine persistende lokale Variable einzurichten: + +.. code-block:: python + + :caption: persistent, lokal zu definierenden Logik + + logic.myvar = 'my Value' + + +kann eine Variable Logik-übergreifend eingerichtet werden. Dann ist als Präfix statt *logic.* +der Präfix *logics.* zu verwenden: + +.. code-block:: python + + :caption: persistent, für alle Logiken zugreifbar + + logics.myvar = 'my Value' + + +Analog zur lokalen persistenten Variable muss die Existenz folgendermaßen sichergestellt werden: + +.. code-block:: python + :caption: Sicherstellen, dass die Variable existiert + + if not hasattr(logics, 'myvar'): + logics.myvar = None + + +Unterschiede zu lokalen persistenten Variablen +---------------------------------------------- + +Eine einmal initialisierte Logik-übergreifende persistente Variable behält ihren Wert bis +zum Neustart von SmartHomeNG. + +.. attention:: + + Da die Logik-übergreifende Variable ihren Wert auch behält, wenn die Logik die sie initialisiert hat + neu geladen wird, kann es zu unerwarteten Ergebnissen kommen, da sich die Logik nun evtl. bei einem + Neustart der Logik anders verhält, als beim Neustart von SmartHomeNG! + diff --git a/doc/user/source/referenz/logiken/logiken_python_module.rst b/doc/user/source/referenz/logiken/logiken_python_module.rst new file mode 100644 index 0000000000..14ef63ea85 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_python_module.rst @@ -0,0 +1,22 @@ +:tocdepth: 5 + +.. index:: Python Module; Logiken +.. index:: Logiken; Python Module + + +============================== +Bereits geladene Python Module +============================== + +Im Logik Environment sind diverse Python Module bereits geladen: + +- sys +- os +- time +- datetime +- ephem +- random +- Queue +- subprocess + + diff --git a/doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst b/doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst new file mode 100644 index 0000000000..d51ebd59fa --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst @@ -0,0 +1,169 @@ +:tocdepth: 5 + +.. index:: SmartHomeNG Methoden; Logiken +.. index:: Logiken; SmartHomeNG Methoden + + +==================== +SmartHomeNG Methoden +==================== + +Datum und Uhrzeit +================= + +sh.now und sh.utcnow +~~~~~~~~~~~~~~~~~~~~ + +Diese beiden Funktionen geben ein ``datetime`` Objekt zurück das die lokale Zeitzone berücksichtigt. +Es ist möglich mit anderen Zeitzonen zu rechnen. +``sh.tzinfo()`` und ``sh.utcinfo()`` geben die lokale Zeitzone und die UTC Zeitzone zurück. + +shtime +------ + +. . . + + +Astronomie +========== + +sh.sun +~~~~~~ + +Dieses Modul gibt Zugriff auf das Sonnenobjekt. +Vorbedingung ist die Definition von Längen- und Breitengrad in ``smarthome.yaml``. + +``sh.sun.pos([offset], [degree=False])`` + Gibt die aktuelle Position der Sonne zurück, optional einen Offset in Minuten and und + ob der Rückgabe in Grad anstatt in Rad erfolgen soll + + ``azimut, altitude = sh.sun.pos()``: + liefert die aktuelle Position der Sonne + + ``azimut, altitude = sh.sun.pos(degree=True)``: + liefert die aktuelle Position der Sonne in Grad + + ``azimut, altitude = sh.sun.pos(30)`` + liefert die Position, die die Sonne in 30 Minuten haben wird + +``sh.sun.set([offset])``: + Gibt den nächsten Sonnenuntergang zurück, optional mit einem Offset in Grad. + + ``sunset = sh.sun.set()``: + Liefert ein auf UTC basierendes ``datetime`` Objekt mit dem nächsten Sonnenuntergang + + ``sunset_tw = sh.sun.set(-6)``: + Liefert ein auf UTC basierendes ``datetime`` Objekt mit der Zeitangabe + des nächsten Sonnenuntergangs zuzüglich der Zeit bis die Dämmerung beendet ist. + +``sh.sun.rise([offset])``: + Gibt analog zu ``set`` den nächsten Sonnenaufgang zurück, optional mit einem Offset in Grad. + + +sh.moon +~~~~~~~ + +Neben den drei Funktionen ``pos``, ``set`` und ``rise`` (wie beim Objekt ``sh.sun``) gibt es noch +zwei weitere Funktionen: + +``sh.moon.light(offset)``: + liefert einen Wert im Bereich [0...100] der hellen Oberfläche zur aktuellen Zeit plus einen Offset + +``sh.moon.phase(offset)``: + Liefert die Mondphase als Ganzzahl Wert im Bereich [0...7]: + 0 = Neumond + 4 = Vollmond + 7 = abnehmender Halbmond + + +Scheduler +========= + +sh.scheduler.trigger() / sh.trigger() +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Diese globale Funktion triggert eine gegebene Logik unter Angabe ihres Namens + +``sh.trigger(name [, by] [, source] [, value] [, dt])`` + ``name``: + der Name der zu triggernden Funktion + + ``by``: + Name einer aufrufenden Logik, der Vorgabewert ist ``Logic`` + + ``source``: + der Grund für das triggern + + ``value`` + eine Variable + + ``dt``: + ein datetime Objekt das die Triggerzeit angibt (lokale Zeitzone berücksichtigt) + +sh.scheduler.change() +~~~~~~~~~~~~~~~~~~~~~ + +Diese Methode ändert Optionen zur Laufzeit der Logiken. Beispiel: + +``sh.scheduler.change('alarmclock', active=False)`` deaktiviert die Logik ``alarmclock`` + +Zusätzlich zum ``active`` parameter können auch ``cron`` und ``cycle`` geändert werden. + + + +sh.tools Objekt +=============== + +Das ``sh.tools`` Objekt stellt folgende nützliche Funktionen zur Verfügung: + +sh.tools.ping() +~~~~~~~~~~~~~~~ + +Sendet ein Ping an einen Computer und liefert das Ergebnis. Beispiel: + +``sh.office.laptop(sh.tools.ping('hostname'))`` + +setzt das Item ``office.laptop`` entsprechend der Rückmeldung ob ein Ping erfolgreich war oder nicht. + +sh.tools.dewpoint() +~~~~~~~~~~~~~~~~~~~ + +Berechnet den Taupunkt für eine gegebene Temperatur und Feuchtigkeit. Beispiel: + +``sh.office.dew(sh.tools.dewpoint(sh.office.temp(), sh.office.hum())`` + +setzt das Item ``office.dew`` auf das Ergebnis der Taupunktberechnung der Itemwerte von ``office.temp`` und ``office.hum`` + +sh.tools.fetch\_url() +~~~~~~~~~~~~~~~~~~~~~ + +Liefert dem Inhalt einer Webseite als String oder ``False`` wenn ein Fehler auftritt. + +``sh.tools.fetch_url('https://www.regular.com')`` + +Es ist möglich als Parameter den Benutzernamen und ein Password anzugeben um die Abfrage bei der zu authentifizieren. + +``sh.tools.fetch_url('https://www.special.com', 'username', 'password')`` + +Weiterhin kann ein Parameter für eine Zeitüberschreitung bestimmt werden: + +``sh.tools.fetch_url('https://www.regular.com', timeout=4)`` + +bricht nach 4 Sekunden ohne Ergebnis ab + +sh.tools.dt2ts(dt) +~~~~~~~~~~~~~~~~~~ + +Wandelt ein datetime Objekt in einen Unix Zeitstempel um. + +sh.tools.dt2js(dt) +~~~~~~~~~~~~~~~~~~ + +Wandelt ein datetime Objekt in einen json Zeitstempel um. + + +sh.tools.rel2abs(temp, hum) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Wandelt einen relativen Feuchtigkeitswert in einen absoluten Feuchtigkeitswert um. + diff --git a/doc/user/source/referenz/logiken/logiken_standardparameter.rst b/doc/user/source/referenz/logiken/logiken_standardparameter.rst new file mode 100644 index 0000000000..5f883e5646 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_standardparameter.rst @@ -0,0 +1,50 @@ + +.. index:: Logiken; Standardparameter +.. index:: Standardparameter; Logiken + + +Standardparameter +================= + +Die folgenden Parameter können für eine Logik in der Konfigurationsdatei im Verzeichnis ../etc angegeben werden. + ++------------------+-----------------------------------------------------------------------------------------------+ +| **Parameter** | **Beschreibung** | ++==================+===============================================================================================+ +| filename | Dateiname des Logik-Codes. Diese Datei muss im Verzeichnis **../logics** liegen. Dieser | +| | Parameter muss angegeben werden. | ++------------------+-----------------------------------------------------------------------------------------------+ +| watch_item | **Optional**: String oder Liste von Strings, die jeweils einen Item-Pfad repräsentieren. | +| | Eine Veränderung eines der hier aufgeführten Items führt dazu, dass die Logik ausgeführt | +| | wird. Details dazu stehen :ref:`hier ` . | ++------------------+-----------------------------------------------------------------------------------------------+ +| crontab | **Optional**: String oder Liste von Strings, die einen crontab Eintrag darstellen. Der Syntax | +| | des Parameters entspricht dem Syntax des **crontab** Attributes von Items. Details dazu | +| | stehen :ref:`hier ` . | ++------------------+-----------------------------------------------------------------------------------------------+ +| cycle | **Optional**: Angabe einer Zykluszeit, die angibt, in welchem Zeitabstand die Logik | +| | periodisch ausgelöst werden soll. Der Syntax des Parameters entspricht dem Syntax des | +| | **cycle** Attributes von Items. Details dazu stehen | +| | :ref:`hier ` . | ++------------------+-----------------------------------------------------------------------------------------------+ +| prio | **Optional**: Angabe einer Priorität für die Logik. Die Priorität kommt nur bei Logiken zum | +| | Einsatz, die ein Schedule haben, bei denen also der Paramter **crontab** oder **cycle** | +| | angegeben wurde. Die Priorität sollte zwischen **2** und **6** liegen. Falls der Parameter | +| | nicht angegeben wird, wird die Standardpriorität **3** verwendet. | ++------------------+-----------------------------------------------------------------------------------------------+ +| visu_acl | **Optional**: Dieser Parameter wird durch das Plugin **visu_websocket** | +| | implementiert. Wenn dieser Parameter auf **True** gesetzt wird, kann die Logik | +| | von einer Visualisierung aus (z.B. smartVISU) ausgelöst werden. | ++------------------+-----------------------------------------------------------------------------------------------+ +| enabled | **Optional**: ``enabled`` kann auf ``False`` gesetzt werden um die Ausführung der Logik | +| | auszusetzen. Der Ausführungsstatus der Logik kann über das CLI-Plugin oder das Admin | +| | Interface gesetzt werden. | ++------------------+-----------------------------------------------------------------------------------------------+ +| | **Optional**: Es können weitere Parameter definiert werden, diese können aus der | +| | Logik heraus abgefragt werden und haben sonst keine Funktion. | ++------------------+-----------------------------------------------------------------------------------------------+ + +Falls keiner der optionalen Parameter **crontab**, **watch_item** oder **cycle** angegeben wird, wird +die Logik nicht automatisiert ausgeführt. Sie kann dann nur aus Plugins oder (falls konfiguriert) über +eine Visualisierung ausgelöst werden. + diff --git a/doc/user/source/referenz/logiken/objekteundmethoden.rst b/doc/user/source/referenz/logiken/objekteundmethoden.rst new file mode 100644 index 0000000000..c9ea812465 --- /dev/null +++ b/doc/user/source/referenz/logiken/objekteundmethoden.rst @@ -0,0 +1,30 @@ + +.. index:: Objekte und Methoden; Logiken +.. index:: Logiken; Objekte und Methoden + +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + +=============================== +Verfügbare Objekte und Methoden +=============================== + + +.. toctree:: + :maxdepth: 3 + :hidden: + :titlesonly: + + objekteundmethoden_logging + objekteundmethoden_feiertage_datum_zeit + objekteundmethoden_sonne_mond + objekteundmethoden_item_methoden + objekteundmethoden_scheduler + objekteundmethoden_tools + + +Neben dem **sh** Objekt, gibt es andere wichtige vordefinierte Objekte: + + diff --git a/doc/user/source/logiken/objekteundmethoden_feiertage_datum_zeit.rst b/doc/user/source/referenz/logiken/objekteundmethoden_feiertage_datum_zeit.rst similarity index 100% rename from doc/user/source/logiken/objekteundmethoden_feiertage_datum_zeit.rst rename to doc/user/source/referenz/logiken/objekteundmethoden_feiertage_datum_zeit.rst diff --git a/doc/user/source/logiken/objekteundmethoden_item_methoden.rst b/doc/user/source/referenz/logiken/objekteundmethoden_item_methoden.rst similarity index 100% rename from doc/user/source/logiken/objekteundmethoden_item_methoden.rst rename to doc/user/source/referenz/logiken/objekteundmethoden_item_methoden.rst diff --git a/doc/user/source/logiken/objekteundmethoden_logging.rst b/doc/user/source/referenz/logiken/objekteundmethoden_logging.rst similarity index 100% rename from doc/user/source/logiken/objekteundmethoden_logging.rst rename to doc/user/source/referenz/logiken/objekteundmethoden_logging.rst diff --git a/doc/user/source/logiken/objekteundmethoden_scheduler.rst b/doc/user/source/referenz/logiken/objekteundmethoden_scheduler.rst similarity index 100% rename from doc/user/source/logiken/objekteundmethoden_scheduler.rst rename to doc/user/source/referenz/logiken/objekteundmethoden_scheduler.rst diff --git a/doc/user/source/logiken/objekteundmethoden_sonne_mond.rst b/doc/user/source/referenz/logiken/objekteundmethoden_sonne_mond.rst similarity index 100% rename from doc/user/source/logiken/objekteundmethoden_sonne_mond.rst rename to doc/user/source/referenz/logiken/objekteundmethoden_sonne_mond.rst diff --git a/doc/user/source/logiken/objekteundmethoden_tools.rst b/doc/user/source/referenz/logiken/objekteundmethoden_tools.rst similarity index 100% rename from doc/user/source/logiken/objekteundmethoden_tools.rst rename to doc/user/source/referenz/logiken/objekteundmethoden_tools.rst diff --git a/doc/user/source/referenz/module/module_websocket.rst b/doc/user/source/referenz/module/module_websocket.rst index 9f1be88f59..97ab055a13 100644 --- a/doc/user/source/referenz/module/module_websocket.rst +++ b/doc/user/source/referenz/module/module_websocket.rst @@ -4,8 +4,8 @@ .. role:: redsup .. role:: bluesup -Module websocket :redsup:`new` -============================== +Module websocket +================ Dieses Modul implementiert die Möglichkeit mit SmartHomeNG über das Websocket Protokoll zu kommunizieren. diff --git a/doc/user/source/referenz/plugins/plugins.rst b/doc/user/source/referenz/plugins/plugins.rst index 0d50d5471e..549ead3269 100644 --- a/doc/user/source/referenz/plugins/plugins.rst +++ b/doc/user/source/referenz/plugins/plugins.rst @@ -2,8 +2,10 @@ .. index:: Referenz; Plugins .. Index:: Plugins; Referenz -.. role:: bluesup .. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup ======= diff --git a/doc/user/source/referenz/referenz.rst b/doc/user/source/referenz/referenz.rst index 04595ce216..f07455efb8 100644 --- a/doc/user/source/referenz/referenz.rst +++ b/doc/user/source/referenz/referenz.rst @@ -1,3 +1,4 @@ +:tocdepth: 5 .. index:: Referenz @@ -14,12 +15,13 @@ können. .. toctree:: - :maxdepth: 4 + :maxdepth: 5 :hidden: :titlesonly: smarthomeng/smarthomeng items/items + logiken/logiken module/module plugins/plugins metadata/metadata diff --git a/doc/user/source/release/1_x_x.rst b/doc/user/source/release/1_x_x.rst new file mode 100644 index 0000000000..b6ab411e7b --- /dev/null +++ b/doc/user/source/release/1_x_x.rst @@ -0,0 +1,185 @@ +========================== +Release 1.x - tt. mmm 2022 +========================== + +Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. + +.. note:: + + Diese Release Notes sind ein Arbeitsstand. + + - Berücksichtigt sind Commits im smarthome Repository bis incl. ... + (...) + - Berücksichtigt sind Commits im plugins Repository bis incl. ... + (...) + + +Überblick +========= + +Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.9.x sind im +folgenden in diesen Release Notes beschrieben. + + +Unterstützte Python Versionen +----------------------------- + +Die älteste offiziell unterstützte Python Version für SmartHomeNG Release 1.8 ist Python 3.6. +(Siehe auch *Hard- u. Software Anforderungen* im Abschnitt *Installation* zu unterstützten Python Versionen) + +.. + Das bedeutet nicht unbedingt, dass SmartHomeNG ab Release 1.8 nicht mehr unter älteren Python Versionen läuft, + sondern das SmartHomeNG nicht mehr mit älteren Python Versionen getestet wird und das gemeldete Fehler mit älteren + Python Versionen nicht mehr zu Buxfixen führen. + + Es werden jedoch zunehmend Features eingesetzt, die erst ab Python 3.6 zur Verfügung stehen. + So ist Python 3.6 die minimale Vorraussetzung zur Nutzung des neuen Websocket Moduls. + + +Minimum Python Version +---------------------- + +Die absolute Minimum Python Version in der SmartHomeNG startet wurde auf v3.6 angehoben, da Python 3.5 im +September 2020 End-of-Life (End of security fixes) gegangen ist. Bei einer Neuinstallation wird jedoch empfohlen +auf einer der neueren Python Versionen (3.7 oder 3.8) aufzusetzen. + +.. important:: + + Mit dem kommenden Release 1.8 werden die unterstützten Python Versionen + :doc:`(wie hier beschrieben) ` auf **Python 3.6, 3.7, 3.8** angehoben. Python 3.6 + hat eine Reihe sehr interessanter Features und Verbesserungen gebracht, die dann in SmartHomeNG genutzt + werden können. + + Sollten solche neuen Features in den Core Einzug halten, wird die **Absolute Minimum Python Version** auf 3.6 + angehoben werden. Sollten die Features nur in Plugins genutzt werden, so können nur solche Plugins nicht genutzt + werden, wenn eine ältere Python Version als 3.6 eingesetzt wird. + + +Änderungen am Core +================== + +Bugfixes in the CORE +-------------------- + +* ... + + +Updates in the CORE +------------------- + +* ... + +* Items: + + * ... + +* Logics: + + * ... + +* Libs: + + * lib. ...: + +* Modules: + + * ...: + + * ... + +* Plugins: + + * ... + +* tests: + + * ... + + +Änderungen bei Plugins +====================== + +New Plugins +----------- + +For details of the changes of the individual plugins, please refer to the documentation of the respective plugin. + +* : ... + + + +Plugin Updates +-------------- + +* : + + * ... + + +Outdated Plugins +---------------- + +The following plugins were already marked in version v1.6 as *deprecated*. This means that the plugins +are still working, but are not developed further anymore and are removed from the release of SmartHomeNG +in the next release. User of these plugins should switch to corresponding succeeding plugins. + +* System Plugins + + * backend - use the administration interface instead + * sqlite_visu2_8 - switch to the **database** plugin + +* Web Plugins + + * wunderground - the free API is not provided anymore by Wunderground + + +The following plugins are marked as *deprecated* with SmartHomeNG v1.7, because neither user nor tester have been found: + +* Gateway Plugins + + * ecmd + * elro + * iaqstick + * snom + * tellstick + +* Interface Plugins + + * easymeter + * smawb + * vr100 + +* Web Plugins + + * nma + +Moreover, the previous mqtt plugin was renamed to mqtt1 and marked as *deprecated*, because the new mqtt +plugin takes over the functionality. This plugin is based on the mqtt module and the recent core. + + +Retired Plugins +--------------- + +The following plugins have been retired. They had been deprecated in one of the preceding releases of SmartHomeNG. +They have been removed from the plugins repository, but they can still be found on github. Now they reside in +the **plugin_archive** repository from where they can be downloaded if they are still needed. + +* ... + + +Weitere Änderungen +================== + +Tools +----- + +* ... + + +Documentation +------------- + +* ... +* ... + + diff --git a/doc/user/source/release/1_x_x.rst.vorlage b/doc/user/source/release/1_x_x.rst.vorlage index ca1231d676..b6ab411e7b 100644 --- a/doc/user/source/release/1_x_x.rst.vorlage +++ b/doc/user/source/release/1_x_x.rst.vorlage @@ -1,5 +1,5 @@ ========================== -Release 1.x - tt. mmm 2021 +Release 1.x - tt. mmm 2022 ========================== Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. @@ -17,7 +17,7 @@ Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. Überblick ========= -Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.8.x sind im +Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.9.x sind im folgenden in diesen Release Notes beschrieben. @@ -77,7 +77,10 @@ Updates in the CORE * ... -* lib....: +* Libs: + + * lib. ...: + * Modules: * ...: @@ -88,6 +91,10 @@ Updates in the CORE * ... +* tests: + + * ... + Änderungen bei Plugins ====================== @@ -118,24 +125,11 @@ in the next release. User of these plugins should switch to corresponding succee * System Plugins - * sqlite - switch to the **database** plugin + * backend - use the administration interface instead * sqlite_visu2_8 - switch to the **database** plugin -* Gateway Plugins - - * tellstick - classic Plugin, not used according to survey in knx-user-forum - -* Interface Plugins - - * netio230b - classic plugin, not used according to survey in knx-user-forum - * smawb - classic plugin, not used according to survey in knx-user-forum - * Web Plugins - * alexa - switch to the **alexa4p3** plugin - * boxcar - classic Plugin, not used according to survey in knx-user-forum - * mail - switch to the **mailsend** and **mailrcv** plugin - * openenergymonitor - classic plugin, not used according to survey in knx-user-forum * wunderground - the free API is not provided anymore by Wunderground @@ -152,13 +146,11 @@ The following plugins are marked as *deprecated* with SmartHomeNG v1.7, because * Interface Plugins * easymeter - * netio230b * smawb * vr100 * Web Plugins - * boxcar * nma Moreover, the previous mqtt plugin was renamed to mqtt1 and marked as *deprecated*, because the new mqtt @@ -170,7 +162,7 @@ Retired Plugins The following plugins have been retired. They had been deprecated in one of the preceding releases of SmartHomeNG. They have been removed from the plugins repository, but they can still be found on github. Now they reside in -the plugin_archive repository from where they can be downloaded if they are still needed. +the **plugin_archive** repository from where they can be downloaded if they are still needed. * ... diff --git a/etc/logging.yaml.default b/etc/logging.yaml.default index 67a96244fc..28ca6746f1 100644 --- a/etc/logging.yaml.default +++ b/etc/logging.yaml.default @@ -212,7 +212,7 @@ loggers: # # Logging items with configuration: log_change: new_device # # to a different file # # - # handlers: [items_file] + # handlers: [shng_items_file] # level: DEBUG # ------------------------------------------ diff --git a/logics/.gitignore b/logics/.gitignore index 276cef36bf..f850825a77 100644 --- a/logics/.gitignore +++ b/logics/.gitignore @@ -3,3 +3,4 @@ # except .gitignore !.gitignore !example_*.* +!logic.tpl diff --git a/logics/logic.tpl b/logics/logic.tpl new file mode 100644 index 0000000000..7adf056233 --- /dev/null +++ b/logics/logic.tpl @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# +# This file contains a logic for use with SmartHomeNG +# +# Name of the logic: example_logic.py +# + +# This logic performs the following function(s): +# +# ... +# + +# The following triggers should be defined in ../etc/logic.yaml: +# +# watch_item = | | ... +# crontab = init = Init +# cycle = 600 +# + +logger.debug(f"Trigger: {trigger}") # When debug logging is enabled for this logic, the values which + # the logic is called with, are logged + # + # To enable debug logging for the logic, add a logger to your logging + # configuration in ../etc/logging.yaml + # loggers: + # ... + # logics: + # handlers: [shng_details_file] + # level: WARNING + # + # logics.example_logic: # has to be the name of the logic config, not the logic's Python file + # level: DEBUG From aff59c6de56da098714dc9da60ec73651e27581a Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 22 Jan 2022 16:44:00 +0100 Subject: [PATCH 42/95] lib.scheduler: Separated code to start a logic task to is's own method --- lib/scheduler.py | 167 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 120 insertions(+), 47 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index f34429a866..8bc694b827 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -622,58 +622,131 @@ def _worker(self): self._runc.release() self._task(name, obj, by, source, dest, value) + + def _start_logic_task(self, logic, by, source, dest, value): + """ + Start a logic from _task method + + :param logic: + :return: + """ + name = 'logics.' + logic.name + logger = logging.getLogger(name) + source_details = None + if isinstance(source, dict): + source_details = source.get('details', '') + src = source.get('item', '') + if src == '': + # get source ('cron' or 'cycle') + src = source.get('source', '') + source = src + trigger = {'by': by, 'source': source, 'source_details': source_details, 'dest': dest, 'value': value} # noqa + + # following variables are assigned to be available during logic execution + sh = self._sh # noqa + shtime = self.shtime + items = self.items + + # set the logic environment here (for use within functions in logics): + #logic = obj # noqa + logic.sh = sh + logic.logger = logger + logic.shtime = shtime + logic.items = items + logic.trigger_dict = trigger # logic.trigger has naming conflict with method logic.trigger of lib.item + + logics = logic._logics + + if not self.mqtt: + if _lib_modules_found: + self.mqtt = Modules.get_instance().get_module('mqtt') + mqtt = self.mqtt + logic.mqtt = mqtt + + try: + if logic.enabled: + exec(logic.bytecode) + # store timestamp of last run + logic.set_last_run() + for method in logic.get_method_triggers(): + try: + method(logic, by, source, dest) + except Exception as e: + logger.exception("Logic: Trigger {} for {} failed: {}".format(method, logic.name, e)) + except SystemExit: + # ignore exit() call from logic. + pass + except Exception as e: + tb = sys.exc_info()[2] + tb = traceback.extract_tb(tb)[-1] + if tb[2] == '': + logic_method = 'Hauptroutine der Logik' + else: + logic_method = tb[2] + '()' + logger.error(f"In der Logik ist ein Fehler aufgetreten:\n Logik '{logic.name}', Datei '{tb[0]}', Zeile {tb[1]}\n Methode '{logic_method}', Exception: '{e}'") + #logger.exception(f"In der Logik ist ein Fehler aufgetreten:\n Logik '{logic.name}', Datei '{tb[0]}', Zeile {tb[1]}\n Methode '{logic_method}', Exception: '{e}'\n ") + + return + + def _task(self, name, obj, by, source, dest, value): threading.current_thread().name = name logger = logging.getLogger(name) if obj.__class__.__name__ == 'Logic': - source_details = None - if isinstance(source, dict): - source_details = source.get('details', '') - src = source.get('item', '') - if src == '': - # get source ('cron' or 'cycle') - src = source.get('source', '') - source = src - trigger = {'by': by, 'source': source, 'source_details': source_details, 'dest': dest, 'value': value} # noqa - - #following variables are assigned to be available during logic execution - sh = self._sh # noqa - shtime = self.shtime - items = self.items - - # set the logic environment here (for use within functions in logics): - logic = obj # noqa - logic.sh = sh - logic.logger = logger - logic.shtime = shtime - logic.items = items - logic.trigger_dict = trigger # logic.trigger has naming conflict with method logic.trigger of lib.item - - logics = obj._logics - - if not self.mqtt: - if _lib_modules_found: - self.mqtt = Modules.get_instance().get_module('mqtt') - mqtt = self.mqtt - logic.mqtt = mqtt + self._start_logic_task(obj, by, source, dest, value) + + # source_details = None + # if isinstance(source, dict): + # source_details = source.get('details', '') + # src = source.get('item', '') + # if src == '': + # # get source ('cron' or 'cycle') + # src = source.get('source', '') + # source = src + # trigger = {'by': by, 'source': source, 'source_details': source_details, 'dest': dest, 'value': value} # noqa + # + # #following variables are assigned to be available during logic execution + # sh = self._sh # noqa + # shtime = self.shtime + # items = self.items + # + # # set the logic environment here (for use within functions in logics): + # logic = obj # noqa + # logic.sh = sh + # logic.logger = logger + # logic.shtime = shtime + # logic.items = items + # logic.trigger_dict = trigger # logic.trigger has naming conflict with method logic.trigger of lib.item + # + # #logics = obj._logics + # logics = logic._logics + # + # if not self.mqtt: + # if _lib_modules_found: + # self.mqtt = Modules.get_instance().get_module('mqtt') + # mqtt = self.mqtt + # logic.mqtt = mqtt + # + # try: + # if logic.enabled: + # #exec(obj.bytecode) + # exec(logic.bytecode) + # # store timestamp of last run + # #obj.set_last_run() + # logic.set_last_run() + # for method in logic.get_method_triggers(): + # try: + # method(logic, by, source, dest) + # except Exception as e: + # logger.exception("Logic: Trigger {} for {} failed: {}".format(method, logic.name, e)) + # except SystemExit: + # # ignore exit() call from logic. + # pass + # except Exception as e: + # tb = sys.exc_info()[2] + # tb = traceback.extract_tb(tb)[-1] + # logger.exception("Logic: {0}, File: {1}, Line: {2}, Method: {3}, Exception: {4}".format(name, tb[0], tb[1], tb[2], e)) - try: - if logic.enabled: - exec(obj.bytecode) - # store timestamp of last run - obj.set_last_run() - for method in logic.get_method_triggers(): - try: - method(logic, by, source, dest) - except Exception as e: - logger.exception("Logic: Trigger {} for {} failed: {}".format(method, logic.name, e)) - except SystemExit: - # ignore exit() call from logic. - pass - except Exception as e: - tb = sys.exc_info()[2] - tb = traceback.extract_tb(tb)[-1] - logger.exception("Logic: {0}, File: {1}, Line: {2}, Method: {3}, Exception: {4}".format(name, tb[0], tb[1], tb[2], e)) elif obj.__class__.__name__ == 'Item': try: if value is not None: From 30d5c7878b7ecde9180fdea5047fc7eaa6a93d45 Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 22 Jan 2022 18:12:28 +0100 Subject: [PATCH 43/95] lib.scheduler: Implemented 'raise LeaveLogic()' for logics. --- lib/scheduler.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index 8bc694b827..ace56cd805 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -62,6 +62,7 @@ from lib.triggertimes import TriggerTimes +class LeaveLogic(Exception): pass # declare a label for 'raise LeaveLogic' class _PriorityQueue: """ @@ -376,7 +377,11 @@ def check_caller(self, name, from_smartplugin=False): :param from_smartplugin: :return: returns either the name or name combined with instance name """ - stack = inspect.stack() + try: + stack = inspect.stack() + except Exception as e: + logger.exception(f"check_caller('{name}'): Exception in inspect.stack(): {e}") + try: obj = stack[2][0].f_locals["self"] if isinstance(obj, SmartPlugin): @@ -665,14 +670,22 @@ def _start_logic_task(self, logic, by, source, dest, value): try: if logic.enabled: - exec(logic.bytecode) - # store timestamp of last run - logic.set_last_run() - for method in logic.get_method_triggers(): - try: - method(logic, by, source, dest) - except Exception as e: - logger.exception("Logic: Trigger {} for {} failed: {}".format(method, logic.name, e)) + if sh.shng_status['code'] < 20: + logger.warning('Logik ignoriert, SmartHomeNG ist noch nicht vollständig initialisiert') + else: + logger.debug(f"Getriggert durch: {trigger}") + exec(logic.bytecode) + # store timestamp of last run + logic.set_last_run() + for method in logic.get_method_triggers(): + try: + method(logic, by, source, dest) + except Exception as e: + logger.exception("Logic: Trigger {} for {} failed: {}".format(method, logic.name, e)) + except LeaveLogic as e: + # 'LeaveLogic' is no error + if str(e) != '': + logger.warning(f"Die Logik '{logic.name}' wurde verlassen. Grund: {e}") except SystemExit: # ignore exit() call from logic. pass From 14b7326adf4883eda61d0cae47ef5a1e8aa98ca5 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 23 Jan 2022 20:15:40 +0100 Subject: [PATCH 44/95] Documentaion updates --- bin/shngversion.py | 4 + doc/user/source/beispiele/eval.rst | 2 +- .../source/entwicklung/logiken/logiken.rst | 8 +- .../konfigurationsdateien/holidays.rst | 2 +- doc/user/source/referenz/items/items.rst | 5 +- doc/user/source/referenz/items/methoden.rst | 94 +++++++++++ .../items/standard_attribute/eval.rst | 6 +- doc/user/source/referenz/logiken/logiken.rst | 10 +- .../logiken/logiken_grundstruktur.rst | 11 +- .../source/referenz/logiken/logiken_items.rst | 81 +-------- .../logiken/logiken_konfiguration.rst | 6 +- .../referenz/logiken/logiken_logging.rst | 31 +++- .../referenz/logiken/logiken_logic_objekt.rst | 150 ++++++++--------- .../logiken/logiken_plugin_funktionen.rst | 16 ++ .../logiken/logiken_python_module.rst | 44 ++++- .../logiken/logiken_smarthomeng_methoden.rst | 121 ++------------ .../logiken/logiken_standardparameter.rst | 2 +- .../referenz/logiken/objekteundmethoden.rst | 30 ---- .../objekteundmethoden_item_methoden.rst | 90 ---------- .../logiken/objekteundmethoden_logging.rst | 56 ------- .../logiken/objekteundmethoden_scheduler.rst | 42 ----- .../logiken/objekteundmethoden_sonne_mond.rst | 41 ----- .../logiken/objekteundmethoden_tools.rst | 76 --------- .../feiertage_datum_zeit.rst} | 0 .../smarthomeng/methoden_sonne_mond.rst | 154 ++++++++++++++++++ 25 files changed, 450 insertions(+), 632 deletions(-) create mode 100644 doc/user/source/referenz/items/methoden.rst create mode 100644 doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst delete mode 100644 doc/user/source/referenz/logiken/objekteundmethoden.rst delete mode 100644 doc/user/source/referenz/logiken/objekteundmethoden_item_methoden.rst delete mode 100644 doc/user/source/referenz/logiken/objekteundmethoden_logging.rst delete mode 100644 doc/user/source/referenz/logiken/objekteundmethoden_scheduler.rst delete mode 100644 doc/user/source/referenz/logiken/objekteundmethoden_sonne_mond.rst delete mode 100644 doc/user/source/referenz/logiken/objekteundmethoden_tools.rst rename doc/user/source/referenz/{logiken/objekteundmethoden_feiertage_datum_zeit.rst => smarthomeng/feiertage_datum_zeit.rst} (100%) create mode 100644 doc/user/source/referenz/smarthomeng/methoden_sonne_mond.rst diff --git a/bin/shngversion.py b/bin/shngversion.py index f07a046fe2..b28b3c2be1 100644 --- a/bin/shngversion.py +++ b/bin/shngversion.py @@ -118,6 +118,10 @@ def _get_git_data(sub='', printout=False): def get_shng_main_version(): return Version.format( shNG_version ) +def get_shng_plugins_version(): + plgversion = get_plugins_version().split('-')[0] + return Version.format( plgversion ) + def get_shng_version(): commit, commit_short, branch, describe = _get_git_data() VERSION = get_shng_main_version() diff --git a/doc/user/source/beispiele/eval.rst b/doc/user/source/beispiele/eval.rst index db5ce48e1a..580d59852b 100644 --- a/doc/user/source/beispiele/eval.rst +++ b/doc/user/source/beispiele/eval.rst @@ -280,7 +280,7 @@ Dieser errechnete Wert wird dann als Dauer für den **autotimer** verwendet. Ab SmartHomeNG v1.7 gibt es hierfür eine Funktion, die in eval Attributen und Logiken verwendet werden kann: **shtime.time_since()** ermöglicht auch die Rückgabe in anderen Einheiten/Formaten als Sekunden. - Zur Verwendung der Funktionen bitte im Abschnitt Referenz/Logiken unter :doc:`Feiertage, Daten und Zeiten ` + Zur Verwendung der Funktionen bitte im Abschnitt Referenz/Logiken unter :doc:`Feiertage, Daten und Zeiten ` nachschauen. Dort sind auch eine Reihe weiterer hilfreicher Funktionen beschrieben. diff --git a/doc/user/source/entwicklung/logiken/logiken.rst b/doc/user/source/entwicklung/logiken/logiken.rst index 59d6e42f5c..f7c571d1c4 100644 --- a/doc/user/source/entwicklung/logiken/logiken.rst +++ b/doc/user/source/entwicklung/logiken/logiken.rst @@ -1,12 +1,14 @@ .. index:: Entwicklung; Logiken -.. role:: bluesup .. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup -Logiken -======= +Logiken :bluesup:`under construction` +===================================== Einführung diff --git a/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst b/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst index 8004b123b4..03c76a4319 100644 --- a/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst +++ b/doc/user/source/konfiguration/konfigurationsdateien/holidays.rst @@ -94,6 +94,6 @@ etwa so aussehen: :language: yaml -Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` +Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` beschrieben, wie die hier konfigurierten Feiertage in Logiken und **eval** Attributen genutzt werden können. diff --git a/doc/user/source/referenz/items/items.rst b/doc/user/source/referenz/items/items.rst index 6ed5f96664..8b3957a7fd 100644 --- a/doc/user/source/referenz/items/items.rst +++ b/doc/user/source/referenz/items/items.rst @@ -2,8 +2,10 @@ .. index:: Referenz; Items .. Index:: Items; Referenz -.. role:: bluesup .. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup ===== @@ -22,3 +24,4 @@ In den folgenden Abschnitten sind Informationen zu den Items in SmartHomeNG zu f properties funktionen systemitems + methoden diff --git a/doc/user/source/referenz/items/methoden.rst b/doc/user/source/referenz/items/methoden.rst new file mode 100644 index 0000000000..e31d009c8d --- /dev/null +++ b/doc/user/source/referenz/items/methoden.rst @@ -0,0 +1,94 @@ + +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + +Methoden zum Zugriff auf Items +============================== + +Die im folgenden beschriebenen Methoden waren ursprünglich Teil des smarthome Objektes ``sh``. +Die Nutzung des ``sh`` Objektes für Items wird nicht weitergeführt. Es ist besser das Item API wie folgt zu nutzen: + +.. code:: python + + from lib.item import Items + items = Items.get_instance() + +.. note:: + + Bei Ausführung einer Logik ist das ``items`` Objekt bereits initialisiert und kann ohne die oben beschriebene + Initialisierung dirket genutzt werden. + + +Mit dem ``items`` Objekt können nun die folgenden Funktionen verwendet werden: + + +return_item(path) +----------------- + +Liefert ein Item Objekt für den angegebenen Pfad zurück. Beispiel: + +.. code-block:: python + + logger.info(items.return_item('first_floor.bath')) + + +return_items() +-------------- + +Liefert alle Item Objekte zurück + +.. code-block:: python + + for item in items.return_items(): + logger.info(item.id()) + + +match_items(regex) +------------------ + +Liefert alle Item Objekte deren Pfad mit einem regulären Ausdruck gefunden wird und die optional ein bestimmtes Attribut aufweisen. + +.. code-block:: python + + for item in items.match_items('*.lights'): # selects all items ending with 'lights' + logger.info(item.id()) + + for item in items.match_items('*.lights:special'): # selects all items ending with 'lights' and attribute 'special' + logger.info(item.id()) + + +find_items(configattribute) +--------------------------- + +Abhängig von ``configattribute`` werden die folgenden Items zurückgegeben: + +.. table:: + + ====================== ========================================================= + Attribut Ergebnis + ====================== ========================================================= + ``attribute`` Nur Items bei denen keine Instanz ID angegeben ist + ``attribute@`` Items mit oder ohne Instanz ID + ``attribute@instance`` Items mit einem bestimmten Attribut und einer Instanz ID + ``@instance`` Items mit einer bestimmten Instanz ID + ====================== ========================================================= + + +.. code:: python + + for item in items.find_items('my_special_attribute'): + logger.info(item.id()) + + +find_children(parentitem, configattribute) +------------------------------------------ + +Liefert alle Kind Item Objekte eines Elternitems mit einem gegebenen ``configattribute``. +Die Suche nach dem ``configattribute`` wird genauso durchgeführt wie in ``find_items(configattribute)`` weiter oben. + +.. code:: python + + for item in items.find_children('my_special_attribute'): + logger.info(item.id()) diff --git a/doc/user/source/referenz/items/standard_attribute/eval.rst b/doc/user/source/referenz/items/standard_attribute/eval.rst index 67bc568b8e..15bef05911 100644 --- a/doc/user/source/referenz/items/standard_attribute/eval.rst +++ b/doc/user/source/referenz/items/standard_attribute/eval.rst @@ -101,8 +101,8 @@ zusätzliche Funktionen in eval Ausdrücken Bereits SmartHomeNG v1.3 wird das Python Modul `math `__ bereitgestellt und es können entsprechende Funktionen genutzt werden. Außerdem sind seit SmarthomeNG v1.7 die -:doc:`Items-API ` als **items** (z.B. items.return_item('bla')) und das -:doc:`shtime Modul ` mittels **shtime** (z.B. shtime.now()) verfügbar. +:doc:`Items-API ` als **items** (z.B. items.return_item('bla')) und das +:doc:`shtime Modul ` mittels **shtime** (z.B. shtime.now()) verfügbar. Beispiel: @@ -127,7 +127,7 @@ auf andere Items beziehen. .. tip:: - Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` + Im Abschnitt **Logiken** ist auf der Seite :doc:`Feiertage, Daten und Zeiten ` beschrieben, welche Feiertags- und Datums-Funktionen in Logiken benutzt werden können. Diese Funktionen können auch in eval Attributen genutzt werden können. diff --git a/doc/user/source/referenz/logiken/logiken.rst b/doc/user/source/referenz/logiken/logiken.rst index 44bb4c3b62..7f46b63d4c 100644 --- a/doc/user/source/referenz/logiken/logiken.rst +++ b/doc/user/source/referenz/logiken/logiken.rst @@ -1,14 +1,14 @@ :tocdepth: 5 -.. index:: Referenz; Logiken -.. Index:: Logiken; Referenz - .. role:: redsup .. role:: bluesup .. role:: greensup .. role:: blacksup +.. index:: Referenz; Logiken +.. Index:: Logiken; Referenz + ##################### Logiken :redsup:`Neu` ##################### @@ -33,10 +33,12 @@ definiert hat). logiken_grundstruktur logiken_konfiguration logiken_logic_objekt - objekteundmethoden logiken_logging + logiken_python_module logiken_smarthomeng_methoden + logiken_plugin_funktionen + logiken_persistente_vars logiken_items logiken_funktionen diff --git a/doc/user/source/referenz/logiken/logiken_grundstruktur.rst b/doc/user/source/referenz/logiken/logiken_grundstruktur.rst index 9a099a0076..35489c46c7 100644 --- a/doc/user/source/referenz/logiken/logiken_grundstruktur.rst +++ b/doc/user/source/referenz/logiken/logiken_grundstruktur.rst @@ -1,12 +1,15 @@ -:tocdepth: 5 +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup .. index:: Struktur; Logiken .. index:: Logiken; Struktur -========================= -Grundstruktur einer Logik -========================= +======================================================= +Grundstruktur einer Logik :bluesup:`under construction` +======================================================= Das eigentliche Python Skript einer Logik muss im Verzeichnis ``../logics`` der SmartHomeNG Installation abgelegt werden. Damit die Logiken getriggert und ausgeführt werden, müssen sie zusätzlich in der Konfigurationsdatei diff --git a/doc/user/source/referenz/logiken/logiken_items.rst b/doc/user/source/referenz/logiken/logiken_items.rst index 24e3d5c4ef..f73589f641 100644 --- a/doc/user/source/referenz/logiken/logiken_items.rst +++ b/doc/user/source/referenz/logiken/logiken_items.rst @@ -37,7 +37,7 @@ gibt den Wert zurück, mit **sh.path.item.property.value = Wert** kann der Wert lässt sich wie eine normale Variablenzuweisung nutzen. Beispiel -======== +-------- Eine Logik sieht prinzipiell folgendermaßen aus: @@ -55,82 +55,7 @@ Eine Logik sieht prinzipiell folgendermaßen aus: -weiteres -======== +.. include:: /referenz/items/properties.rst -Methoden zum Zugriff auf Items ------------------------------- - -Die Nutzung des ``sh`` Objektes für Items wird nicht weitergeführt. Es ist besser das Item API wie folgt zu nutzen: - -.. code:: python - - from lib.item import Items - items = Items.get_instance() - -Mit dem ``items`` Objekt können nun die folgenden Funktionen verwendet werden: - - -items.return_item(path) -~~~~~~~~~~~~~~~~~~~~~~~ - -Liefert ein Item Objekt für den angegebenen Pfad zurück. Beispiel: - -``items.return_item('first_floor.bath')`` - -items.return_items() -~~~~~~~~~~~~~~~~~~~~ - -Liefert alle Item Objekte zurück - -.. code-block:: python - - for item in items.return_items(): - logger.info(item.id()) - -items.match_items(regex) -~~~~~~~~~~~~~~~~~~~~~~~~ - -Liefert alle Item Objekte deren Pfad mit einem regulären Ausdruck gefunden wird und die optional ein bestimmtes Attribut aufweisen. - -.. code-block:: python - - for item in items.match_items('*.lights'): # selects all items ending with 'lights' - logger.info(item.id()) - - for item in items.match_items('*.lights:special'): # selects all items ending with 'lights' and attribute 'special' - logger.info(item.id()) - -items.find_items(configattribute) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Abhängig von ``configattribute`` werden die folgenden Items zurückgegeben: - -.. table:: - - ====================== ========================================================= - Attribut Ergebnis - ====================== ========================================================= - ``attribute`` Nur Items bei denen keine Instanz ID angegeben ist - ``attribute@`` Items mit oder ohne Instanz ID - ``attribute@instance`` Items mit einem bestimmten Attribut und einer Instanz ID - ``@instance`` Items mit einer bestimmten Instanz ID - ====================== ========================================================= - - -.. code:: python - - for item in items.find_items('my_special_attribute'): - logger.info(item.id()) - -find\_children(parentitem, configattribute): -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Liefert alle Kind Item Objekte eines Elternitems mit einem gegebenen ``configattribute``. -Die Suche nach dem ``configattribute`` wird genauso durchgeführt wie in ``find_items(configattribute)`` weiter oben. - -... - -- Methoden -- Item Werte und Attribute +.. include:: /referenz/items/methoden.rst diff --git a/doc/user/source/referenz/logiken/logiken_konfiguration.rst b/doc/user/source/referenz/logiken/logiken_konfiguration.rst index 4c31110866..15e81064b8 100644 --- a/doc/user/source/referenz/logiken/logiken_konfiguration.rst +++ b/doc/user/source/referenz/logiken/logiken_konfiguration.rst @@ -210,9 +210,9 @@ prio ~~~~ Setzt die Priorität der Logik im Kontext des Schedulers. -Jeder Wert zwischen ``1`` und ``10`` ist erlaubt mit ``1`` für die höchste Priorität und ``10`` die niedrigste. +Jeder Wert zwischen ``1`` und ``5`` ist erlaubt mit ``1`` für die höchste Priorität und ``5`` die niedrigste. Im Normalfall ist eine Angabe der Priorität nicht notwendig, die Vorgabe für alle Logiken ohne -Prioritätsangabe ist ``5``. +Prioritätsangabe ist ``3``. .. _logik_parameter_user_parameter: @@ -227,5 +227,5 @@ Im ersten Beispiel ist für die vierte definierte Logik ein Parameter ``usage_wa -Details zur Erstellung von Logiken finden sich unter :doc:`Logiken ` +Details zur Erstellung von Logiken finden sich unter :doc:`Logiken ` und :doc:`Entwicklung/Logiken `. \ No newline at end of file diff --git a/doc/user/source/referenz/logiken/logiken_logging.rst b/doc/user/source/referenz/logiken/logiken_logging.rst index acd8dcf4d9..6aa118e0da 100644 --- a/doc/user/source/referenz/logiken/logiken_logging.rst +++ b/doc/user/source/referenz/logiken/logiken_logging.rst @@ -1,13 +1,18 @@ -:tocdepth: 5 .. index:: Logging; Logging in Logiken .. index:: Logiken; Logging in Logiken +.. role:: bluesup +.. role:: redsup + ================== Logging in Logiken ================== +Logger Konfiguration +==================== + Damit aus Logiken heraus geloggt werden kann, muss dafür in der Logging-Konfigurationsdatei ``etc/logging.yaml`` ein entsprechender Logger konfiguriert sein. @@ -54,3 +59,27 @@ Für eine Logik mit dem Namen ``example``, sieht das beispielsweise folgenderma Als Handler wird dabei der bereits im Logger ``logics`` definierte Handler verwendet. Es können bei Bedarf im Logger der einzelnen Logik zusätzliche handler angegeben werden. Dabei muss darauf geachtet werden, dass der im Logger ``logics`` definierte Handler nicht erneut angegeben wird, sa sonst die Logausgaben doppelt erfolgen. + + + +Logging im Code +=============== + +Nachdem das Logging für die Logik/Logiken wie oben beschrieben konfiguriert wurde, können aus dem Code der Logik +heraus folgendermaßen Logeinträge erstellt werden: + +.. code-block:: python + + logger.error("Logtext der mit dem Level ERROR geloggt wird") + + logger.warning("Logtext der mit dem Level WARNING geloggt wird") + + logger.info("Logtext der mit dem Level INFO geloggt wird") + + logger.debug("Logtext der mit dem Level DEBUG geloggt wird") + + +Zusätzlich zu den Logeinträgen die in der Logik explizit erzeugt werden, wird vor Aufruf der Logik ein Logeintrag +erzeugt, der anzeigt, was die Ausführung der Logik getriggert hat. Dieser Logeintrag wird im Level DEBUG geschrieben. +Er erscheint in den Logdateien also nur, wenn der Loglevel für die entsprechende Logik auf DEBUG gesetzt ist. + diff --git a/doc/user/source/referenz/logiken/logiken_logic_objekt.rst b/doc/user/source/referenz/logiken/logiken_logic_objekt.rst index 63b8011996..b551f23589 100644 --- a/doc/user/source/referenz/logiken/logiken_logic_objekt.rst +++ b/doc/user/source/referenz/logiken/logiken_logic_objekt.rst @@ -11,116 +11,99 @@ Die Objekte **logic** und **logics** .. index:: logic Objekt; Logiken .. index:: Logiken; logic Objekt -Das logic Objekt -================ +Das ``logic`` Objekt +==================== + +Dieses Objekt bietet Zugriff auf das aktuelle Logikobjekt. Es ist möglich, während der Laufzeit die meisten +logischen Attribute (wie crontab, cycle, ...) abzufragen und ändern. Diese Änderungen gehen nach dem Neustart +von SmartHomeNG verloren, da sie nicht in die Konfigurationsdatei ``etc/logic.yaml`` geschrieben werden. + +Ein Zugriff auf das Objekt ``logic`` ist nur aus der Logik selbst zur Laufzeit möglich. -Dieses Objekt bietet Zugriff auf das aktuelle Logikobjekt. Es ist möglich, während der Laufzeit -logische Attribute (crontab, cycle, ...) abzufragen und ändern. Diese Änderungen gehen nach dem Neustart -von SmartHomeNG verloren. Definierte Methoden des Logikobjekts: -+-------------------+--------------------------------------------------------------------------------------------------------+ -| Methode | Erläuterung | -+===================+========================================================================================================+ -| logic.id() | Diese Methode liefert dem Namen der Logik wie in **../etc/logic.yaml** angegeben. | -+-------------------+--------------------------------------------------------------------------------------------------------+ -| logic.last_run() | Diese Methode liefert den letzten Lauf dieser Logik (vor aktuellen Lauf). | -+-------------------+--------------------------------------------------------------------------------------------------------+ -| logic.disable() | Konfigurierte Logiken sind standardmäßig aktiv und werden entsprechend der Konfiguration ausgeführt. | -| | Diese Methode deaktiviert die Logik, sodass deren Ausführung unterbunden wird. (Ab SmartHomeNG v1.3) | -+-------------------+--------------------------------------------------------------------------------------------------------+ -| logic.enable() | Eine bereits deaktivierte Logik kann mit dieser Methode wieder aktiviert werden. (Ab SmartHomeNG v1.3) | -+-------------------+--------------------------------------------------------------------------------------------------------+ ++----------------------+--------------------------------------------------------------------------------------------------------+ +| Methode | Erläuterung | ++======================+========================================================================================================+ +| ``logic.id()`` | Diese Methode liefert dem Namen der Logik wie in **../etc/logic.yaml** angegeben. | ++----------------------+--------------------------------------------------------------------------------------------------------+ +| ``logic.last_run()`` | Diese Methode liefert den letzten Lauf dieser Logik (vor aktuellen Lauf). | ++----------------------+--------------------------------------------------------------------------------------------------------+ +| ``logic.disable()`` | Konfigurierte Logiken sind standardmäßig aktiv und werden entsprechend der Konfiguration ausgeführt. | +| | Diese Methode deaktiviert die Logik, sodass deren Ausführung unterbunden wird. (Ab SmartHomeNG v1.3) | ++----------------------+--------------------------------------------------------------------------------------------------------+ +| ``logic.enable()`` | Eine bereits deaktivierte Logik kann mit dieser Methode wieder aktiviert werden. (Ab SmartHomeNG v1.3) | ++----------------------+--------------------------------------------------------------------------------------------------------+ Vordefinierte Attribute des Logikobjekts: -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| Attribut | Erläuterung | -+===========================+========================================================================================================+ -| trigger[] | Ein Python-Dictionary, welches im Folgenden beschreiben wird. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.name | Das Attribut logic.name liefert das selbe Ergebnis wie die Methode logic.id() | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.crontab | Das Attribut liefert das aktuelle **crontab** Setting dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.cycle | Das Attribut liefert das aktuelle **cycle** Setting dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.prio | Das Attribut liefert das aktuelle **prio** Setting dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic.filename | Das Attribut liefert den Dateinamen des Python Skripts dieser Logik. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| logic. | Liefert den konfigurierten Parameter oder den Wert einer in einem vorherigen Lauf dieser | -| | Logik persistieren Variablen. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ - -> - - - - - - -Dieses Objekt bietet Zugriff auf das aktuelle Logik Objekt. -Es ist möglich die Attribute wie ``crontab``, ``cycle``, etc. während der Laufzeit zu ändern. -Die Änderungen werden aber nicht in die ``logic.yaml`` geschrieben und sind nach einem -Neustart von SmartHomeNG verloren. - -``logic.alive`` - Der Code ``while logic.alive:`` erzeugt eine Endlos-Schleife die bis zum Beenden - von SmartHomeNG läuft. - -``logic.name``: - Liefert den Namen der Logik wie in ``logic.yaml`` definiert - -``logic.last_time()``: - Diese Funktion liefert die letzte Ausführungszeit der Logik vor dem aktuellen Aufruf - -``logic.prio``: - Lesen und setzen der Priorität dieser Logik - -``logic.trigger()``: - Wie ``sh.trigger()``, aber triggert nur die aktuelle Logik. Diese Funktion ist nützlich - um die Logik zu einem späteren Zeitpunkt (noch einmal) auszuführen - -> - - - - - ++--------------------------------+-----------------------------------------------------------------------------------------+ +| Attribut | Erläuterung | ++================================+=========================================================================================+ +| ``logic.name`` | Das Attribut logic.name liefert das selbe Ergebnis wie die Methode logic.id() | ++--------------------------------+-----------------------------------------------------------------------------------------+ +| ``logic.crontab`` | Das Attribut liefert das aktuelle **crontab** Setting dieser Logik. | ++--------------------------------+-----------------------------------------------------------------------------------------+ +| ``logic.cycle`` | Das Attribut liefert das aktuelle **cycle** Setting dieser Logik. | ++--------------------------------+-----------------------------------------------------------------------------------------+ +| ``logic.prio`` | Das Attribut liefert das aktuelle **prio** Setting dieser Logik. | ++--------------------------------+-----------------------------------------------------------------------------------------+ +| ``logic.filename`` | Das Attribut liefert den Dateinamen des Python Skripts dieser Logik. | ++--------------------------------+-----------------------------------------------------------------------------------------+ +| ``logic.`` | Liefert den konfigurierten Parameter oder den Wert einer | +| | in einem vorherigen Lauf dieser Logik persistieren Variabl | ++--------------------------------+-----------------------------------------------------------------------------------------+ + +Zusätzlich gibt es noch das Dictionary ``trigger``, welches Infromationen dazu enthält, wodurch der aktuelle Lauf +der Logik getriggert wurde. + + .. index:: trigger dict; Logiken .. index:: Logiken; trigger dict -Das **trigger** Dictionary +Das ``trigger`` Dictionary -------------------------- -Das **trigger** dict ist ein Python-Dictionary, welches als Laufzeitumgebung einige Informationen über das +Das ``trigger`` dict ist ein Python-Dictionary, welches als Laufzeitumgebung einige Informationen über das Ereignis liefert, das die Logik ausgelöst hat. Das Dictionary enthält folgende Informationen: -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| Attribut/Funktion | Erläuterung | -+===========================+========================================================================================================+ -| trigger['by'] | Auslöser ('Scheduler', Item', etc.) | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['source'] | enthält den Pfad des Items, welches die Logik getriggert hat. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['source_details'] | Falls eine Logik aus einem Item heraus getriggert wurde (also trigger['by'] == Item ist), enthält | -| | trigger['source_details'] weitere Details zum Auslöser (Beispiel: 'knx:1.1.241:ga=3/3/5') | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['dest'] | | -+---------------------------+--------------------------------------------------------------------------------------------------------+ -| trigger['value'] | enthält den Wert des Items, dass die Logik getriggert hat. | -+---------------------------+--------------------------------------------------------------------------------------------------------+ ++-------------------------------+--------------------------------------------------------------------------------------------------------+ +| Attribut/Funktion | Erläuterung | ++===============================+========================================================================================================+ +| ``trigger['by']`` | Auslöser (``Scheduler``, ``Item``, etc.) | ++-------------------------------+--------------------------------------------------------------------------------------------------------+ +| ``trigger['source']`` | enthält beim Auslöser ``Item`` den Pfad des Items, welches die Logik getriggert hat und beim | +| | Auslöser ``Scheduler`` die Information ob es ein ``cron`` oder ein ``cycle`` Eintrag war. | ++-------------------------------+--------------------------------------------------------------------------------------------------------+ +| ``trigger['source_details']`` | Falls eine Logik aus einem Item heraus getriggert wurde (also trigger['by'] == Item ist), enthält | +| | ``trigger['source_details']`` weitere Details zum Auslöser (Beispiel: 'knx:1.1.241:ga=3/3/5') | +| | Falls der Auslöser der Scheduler war, enthält ``trigger['source_details']`` die Zyklus Teit oder den | +| | auslösenden ``crontab`` Eintrag. | ++-------------------------------+--------------------------------------------------------------------------------------------------------+ +| ``trigger['dest']`` | | ++-------------------------------+--------------------------------------------------------------------------------------------------------+ +| ``trigger['value']`` | enthält den Wert des Items, dass die Logik getriggert hat. | ++-------------------------------+--------------------------------------------------------------------------------------------------------+ .. index:: logics Objekt; Logiken .. index:: Logiken; logics Objekt -Das logics Objekt -================= +Das ``logics`` Objekt +===================== Zugriff auf das Logics-API über das logics Objekt: +---------------------------------+---------------------------------------------------------------------------------------------------------+ | Methode | Erläuterung | +=================================+=========================================================================================================+ -| logics. | ermöglicht den Zugriff auf das Logics API, welches in der Developer Dokumentation beschrieben ist. | -| | Im folgenden sind einige Beispiele aufgeführt: | +| logics. | ermöglicht den Zugriff auf das Logics API, welches unter **APIs von SmartHomeNG** im Abschnitt | +| | **Entwicklung** beschrieben ist. Im folgenden sind einige Beispiele aufgeführt. | +---------------------------------+---------------------------------------------------------------------------------------------------------+ | logics.scheduler_add() | Hinzufügen eines Scheduler Eintrages für den logics-Namensraum. Der Syntax entspricht der | | | scheduler.add() Methode. | @@ -130,10 +113,11 @@ Zugriff auf das Logics-API über das logics Objekt: +---------------------------------+---------------------------------------------------------------------------------------------------------+ | logics.scheduler_remove() | Löschen eines Scheduler Eintrages im logics-Namensraum. Der Syntax entspricht der scheduler_remove() | +---------------------------------+---------------------------------------------------------------------------------------------------------+ -| logics.trigger_logic() | Triggern einer im Logik | +| logics.trigger() | Triggern einer im Logik | +---------------------------------+---------------------------------------------------------------------------------------------------------+ | logics.set_config_section_key() | Setzt den Wert eines Schlüssels für eine angegebene Logik (Abschnitt) permanent in ../etc/logic.yaml | +---------------------------------+---------------------------------------------------------------------------------------------------------+ -Der vollständige Syntax der Methoden kann der `Entwickler Dokumentation `_ entnommen werden. +Der vollständige Syntax der Methoden kann im Abschnitt :doc:`Entwicklung/APIs von SmartHomeNG ` +dem **Logics-API** entnommen werden. diff --git a/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst b/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst new file mode 100644 index 0000000000..78b9ec2cf1 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst @@ -0,0 +1,16 @@ + +:tocdepth: 5 + +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + + +.. index:: Logiken; Plgin Funktionen + +=========================================================== +Nutzung von Plugin Funktionen :bluesup:`under construction` +=========================================================== + +. . . diff --git a/doc/user/source/referenz/logiken/logiken_python_module.rst b/doc/user/source/referenz/logiken/logiken_python_module.rst index 14ef63ea85..b4b4971b11 100644 --- a/doc/user/source/referenz/logiken/logiken_python_module.rst +++ b/doc/user/source/referenz/logiken/logiken_python_module.rst @@ -1,5 +1,10 @@ :tocdepth: 5 +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + .. index:: Python Module; Logiken .. index:: Logiken; Python Module @@ -8,15 +13,36 @@ Bereits geladene Python Module ============================== -Im Logik Environment sind diverse Python Module bereits geladen: +Im Logik Environment sind diverse Python Module bereits geladen. Auf die Funktionen der folgenden Python Packages +kann also zugegriffen werden, ohne sie vorher explizit importieren zu müssen. + +Standard Python Packages: + ++-----------------+------------------------------------------------+ +| Package | Beschreibung | ++=================+================================================+ +| ``sys`` | System-specific parameters and functions | ++-----------------+------------------------------------------------+ +| ``os`` | Miscellaneous operating system interfaces | ++-----------------+------------------------------------------------+ +| ``time`` | Time access and conversions | ++-----------------+------------------------------------------------+ +| ``datetime`` | Basic date and time types | ++-----------------+------------------------------------------------+ +| ``random`` | Generate pseudo-random numbers | ++-----------------+------------------------------------------------+ +| ``queue`` | A synchronized queue class | ++-----------------+------------------------------------------------+ +| ``subprocess`` | Subprocess management | ++-----------------+------------------------------------------------+ + + +weitere Packages von pypi.org: -- sys -- os -- time -- datetime -- ephem -- random -- Queue -- subprocess ++-----------------+------------------------------------------------+ +| Package | Beschreibung | ++=================+================================================+ +| ``ephem`` | Compute positions of the planets and stars | ++-----------------+------------------------------------------------+ diff --git a/doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst b/doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst index d51ebd59fa..ea51441d36 100644 --- a/doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst +++ b/doc/user/source/referenz/logiken/logiken_smarthomeng_methoden.rst @@ -8,106 +8,10 @@ SmartHomeNG Methoden ==================== -Datum und Uhrzeit -================= +.. include:: /referenz/smarthomeng/feiertage_datum_zeit.rst -sh.now und sh.utcnow -~~~~~~~~~~~~~~~~~~~~ -Diese beiden Funktionen geben ein ``datetime`` Objekt zurück das die lokale Zeitzone berücksichtigt. -Es ist möglich mit anderen Zeitzonen zu rechnen. -``sh.tzinfo()`` und ``sh.utcinfo()`` geben die lokale Zeitzone und die UTC Zeitzone zurück. - -shtime ------- - -. . . - - -Astronomie -========== - -sh.sun -~~~~~~ - -Dieses Modul gibt Zugriff auf das Sonnenobjekt. -Vorbedingung ist die Definition von Längen- und Breitengrad in ``smarthome.yaml``. - -``sh.sun.pos([offset], [degree=False])`` - Gibt die aktuelle Position der Sonne zurück, optional einen Offset in Minuten and und - ob der Rückgabe in Grad anstatt in Rad erfolgen soll - - ``azimut, altitude = sh.sun.pos()``: - liefert die aktuelle Position der Sonne - - ``azimut, altitude = sh.sun.pos(degree=True)``: - liefert die aktuelle Position der Sonne in Grad - - ``azimut, altitude = sh.sun.pos(30)`` - liefert die Position, die die Sonne in 30 Minuten haben wird - -``sh.sun.set([offset])``: - Gibt den nächsten Sonnenuntergang zurück, optional mit einem Offset in Grad. - - ``sunset = sh.sun.set()``: - Liefert ein auf UTC basierendes ``datetime`` Objekt mit dem nächsten Sonnenuntergang - - ``sunset_tw = sh.sun.set(-6)``: - Liefert ein auf UTC basierendes ``datetime`` Objekt mit der Zeitangabe - des nächsten Sonnenuntergangs zuzüglich der Zeit bis die Dämmerung beendet ist. - -``sh.sun.rise([offset])``: - Gibt analog zu ``set`` den nächsten Sonnenaufgang zurück, optional mit einem Offset in Grad. - - -sh.moon -~~~~~~~ - -Neben den drei Funktionen ``pos``, ``set`` und ``rise`` (wie beim Objekt ``sh.sun``) gibt es noch -zwei weitere Funktionen: - -``sh.moon.light(offset)``: - liefert einen Wert im Bereich [0...100] der hellen Oberfläche zur aktuellen Zeit plus einen Offset - -``sh.moon.phase(offset)``: - Liefert die Mondphase als Ganzzahl Wert im Bereich [0...7]: - 0 = Neumond - 4 = Vollmond - 7 = abnehmender Halbmond - - -Scheduler -========= - -sh.scheduler.trigger() / sh.trigger() -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Diese globale Funktion triggert eine gegebene Logik unter Angabe ihres Namens - -``sh.trigger(name [, by] [, source] [, value] [, dt])`` - ``name``: - der Name der zu triggernden Funktion - - ``by``: - Name einer aufrufenden Logik, der Vorgabewert ist ``Logic`` - - ``source``: - der Grund für das triggern - - ``value`` - eine Variable - - ``dt``: - ein datetime Objekt das die Triggerzeit angibt (lokale Zeitzone berücksichtigt) - -sh.scheduler.change() -~~~~~~~~~~~~~~~~~~~~~ - -Diese Methode ändert Optionen zur Laufzeit der Logiken. Beispiel: - -``sh.scheduler.change('alarmclock', active=False)`` deaktiviert die Logik ``alarmclock`` - -Zusätzlich zum ``active`` parameter können auch ``cron`` und ``cycle`` geändert werden. +.. include:: /referenz/smarthomeng/methoden_sonne_mond.rst @@ -117,7 +21,7 @@ sh.tools Objekt Das ``sh.tools`` Objekt stellt folgende nützliche Funktionen zur Verfügung: sh.tools.ping() -~~~~~~~~~~~~~~~ +--------------- Sendet ein Ping an einen Computer und liefert das Ergebnis. Beispiel: @@ -126,7 +30,7 @@ Sendet ein Ping an einen Computer und liefert das Ergebnis. Beispiel: setzt das Item ``office.laptop`` entsprechend der Rückmeldung ob ein Ping erfolgreich war oder nicht. sh.tools.dewpoint() -~~~~~~~~~~~~~~~~~~~ +------------------- Berechnet den Taupunkt für eine gegebene Temperatur und Feuchtigkeit. Beispiel: @@ -134,8 +38,8 @@ Berechnet den Taupunkt für eine gegebene Temperatur und Feuchtigkeit. Beispiel: setzt das Item ``office.dew`` auf das Ergebnis der Taupunktberechnung der Itemwerte von ``office.temp`` und ``office.hum`` -sh.tools.fetch\_url() -~~~~~~~~~~~~~~~~~~~~~ +sh.tools.fetch_url() +-------------------- Liefert dem Inhalt einer Webseite als String oder ``False`` wenn ein Fehler auftritt. @@ -152,18 +56,25 @@ Weiterhin kann ein Parameter für eine Zeitüberschreitung bestimmt werden: bricht nach 4 Sekunden ohne Ergebnis ab sh.tools.dt2ts(dt) -~~~~~~~~~~~~~~~~~~ +------------------ Wandelt ein datetime Objekt in einen Unix Zeitstempel um. sh.tools.dt2js(dt) -~~~~~~~~~~~~~~~~~~ +------------------ Wandelt ein datetime Objekt in einen json Zeitstempel um. sh.tools.rel2abs(temp, hum) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------- Wandelt einen relativen Feuchtigkeitswert in einen absoluten Feuchtigkeitswert um. + +sh.tools.runtime() +------------------ + +Liefert die Laufzeit von SmartHomeNG zurück. + + diff --git a/doc/user/source/referenz/logiken/logiken_standardparameter.rst b/doc/user/source/referenz/logiken/logiken_standardparameter.rst index 5f883e5646..44f1b1b830 100644 --- a/doc/user/source/referenz/logiken/logiken_standardparameter.rst +++ b/doc/user/source/referenz/logiken/logiken_standardparameter.rst @@ -29,7 +29,7 @@ Die folgenden Parameter können für eine Logik in der Konfigurationsdatei im Ve +------------------+-----------------------------------------------------------------------------------------------+ | prio | **Optional**: Angabe einer Priorität für die Logik. Die Priorität kommt nur bei Logiken zum | | | Einsatz, die ein Schedule haben, bei denen also der Paramter **crontab** oder **cycle** | -| | angegeben wurde. Die Priorität sollte zwischen **2** und **6** liegen. Falls der Parameter | +| | angegeben wurde. Die Priorität sollte zwischen **1** und **5** liegen. Falls der Parameter | | | nicht angegeben wird, wird die Standardpriorität **3** verwendet. | +------------------+-----------------------------------------------------------------------------------------------+ | visu_acl | **Optional**: Dieser Parameter wird durch das Plugin **visu_websocket** | diff --git a/doc/user/source/referenz/logiken/objekteundmethoden.rst b/doc/user/source/referenz/logiken/objekteundmethoden.rst deleted file mode 100644 index c9ea812465..0000000000 --- a/doc/user/source/referenz/logiken/objekteundmethoden.rst +++ /dev/null @@ -1,30 +0,0 @@ - -.. index:: Objekte und Methoden; Logiken -.. index:: Logiken; Objekte und Methoden - -.. role:: redsup -.. role:: bluesup -.. role:: greensup -.. role:: blacksup - -=============================== -Verfügbare Objekte und Methoden -=============================== - - -.. toctree:: - :maxdepth: 3 - :hidden: - :titlesonly: - - objekteundmethoden_logging - objekteundmethoden_feiertage_datum_zeit - objekteundmethoden_sonne_mond - objekteundmethoden_item_methoden - objekteundmethoden_scheduler - objekteundmethoden_tools - - -Neben dem **sh** Objekt, gibt es andere wichtige vordefinierte Objekte: - - diff --git a/doc/user/source/referenz/logiken/objekteundmethoden_item_methoden.rst b/doc/user/source/referenz/logiken/objekteundmethoden_item_methoden.rst deleted file mode 100644 index 033cc85ee7..0000000000 --- a/doc/user/source/referenz/logiken/objekteundmethoden_item_methoden.rst +++ /dev/null @@ -1,90 +0,0 @@ - -.. role:: bluesup -.. role:: redsup - -============= -Item Methoden -============= - -Die grundsätzlichen Methoden, die jedes Item hat, sind unter **Items** beschrieben. Darüber -hinaus stehen die nachfolgend beschrieben Methoden zum Handling von Items zur Verfügung. - -Die Nutzung des SmartHomeNG Objektes ``sh`` ist dabei als veraltet anzusehen. Die richtige -Methode ist die Nutzung des Item API. Das ``items`` Objekt kann wie folgt -genutzt werden: - -.. code:: python - - from lib.item import Items - items = Items.get_instance() - -Nun können folgende Funktionen aufgerufen werden: - -return_item(path) -================= - -Liefert das Item Objekt für den angegebenen Pfad zurück. - -.. code:: python - - items.return_item('erdgeschoss.flur') - - -return_items() -============== - -Liefert sämtliche definierten Item Objekte zurück. - -.. code:: python - - for item in items.return_items(): - logger.info(item.id()) - - -match_items(regex) -================== - -Liefert alle Items zurück, die der Regular Expression, dem Pfad und dem optionalen Attribut entsprechen. - -.. code:: python - - for item in items.match_items('*.licht'): - # Selektiere alle Items, deren Pfad mit 'licht' endet - logger.info(item.id()) - - for item in items.match_items('*.licht:special'): - # Selektiere alle Items, deren Pfad mit 'licht' endet und die das Attribut 'special' haben - logger.info(item.id()) - - -find_items(configattribute) -=========================== - -In Abhängigkeit von ``configattribute`` werden wie folgt Items zurückgeliefert: - -.. table:: - - ==================== ========================================= - Attribut Ergebnis - ==================== ========================================= - ``Attribut`` Nur Items ohne Angabe von Instanz Kennung - ``Attribut@`` Items mit und ohne Instanz Kennung - ``Attribut@Instanz`` Items mit exakter Übereinstimmung von Attribut und Instanz Kennung - ``@Instanz`` Items die mit dieser Instanz Kennung definiert worden sind - ==================== ========================================= - - -.. code:: python - - for item in items.find_items('my_special_attribute'): - logger.info(item.id()) - - -find_children(parentitem, configattribute) -========================================== - -Diese Funktion liefert ausgehend vom Item ``parentitem`` alle Items mit passendem -``configattribute``. Dabei wird die Suche nach dem Attribut exakt so ausgeführt wie -in der Funktion ``find_items(configattribute)`` zuvor beschrieben. - - diff --git a/doc/user/source/referenz/logiken/objekteundmethoden_logging.rst b/doc/user/source/referenz/logiken/objekteundmethoden_logging.rst deleted file mode 100644 index feb26b0b57..0000000000 --- a/doc/user/source/referenz/logiken/objekteundmethoden_logging.rst +++ /dev/null @@ -1,56 +0,0 @@ - -.. index:: Logiken; Logging -.. index:: Logging; Logiken - -.. role:: bluesup -.. role:: redsup - - -Logging in Logiken :redsup:`Neu` -================================ - -Logiken verfügen über einen vordefinierten Logger. Dieser Logger ist im ``logic`` Objekt hinterlegt und kann -z.B. über - -.. code-block:: python - - logic.logger.info("Logtext") - -angesprochen werden. Die Ausgabe und er Loglevel können in ../etc/logging.yaml konfiguriert werden: - -.. code-block:: yaml - - loggers: - - # ============================== - # Loggers for SmartHomeNG logics - # ------------------------------ - logics: - handlers: [shng_details_file] - level: WARNING - - logics.: - handlers: [q21] - level: INFO - - -Wobei der Logger **logics** den Loglevel und Handler für alle Logiken festlegt, für die keine besonderen -Konfigurationen vorgenommen werden. - -**logics.** Legt den Loglevel und den Handler für eine einzelne Logik fest. - -.. note:: - - In SmartHomeNG v1.7.1 und davor, gab es diesen vordefinierten Logger noch nicht, - so dass in den Logiken der ein Logger explizit definiert werden musste: - - .. code-block:: python - - import logging - logger = logging.getLogger(__name__) - - ... - - logger.info("Logtext") - - diff --git a/doc/user/source/referenz/logiken/objekteundmethoden_scheduler.rst b/doc/user/source/referenz/logiken/objekteundmethoden_scheduler.rst deleted file mode 100644 index f02ccc7f68..0000000000 --- a/doc/user/source/referenz/logiken/objekteundmethoden_scheduler.rst +++ /dev/null @@ -1,42 +0,0 @@ -Scheduler Methoden ------------------- - -sh.scheduler.trigger() / sh.trigger() -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Diese globale Funktion triggert eine Logik durch die Angabe ihres Namens. - -.. code-block:: python - - sh.trigger(name [, by] [, source] [, value] [, dt]) - -- `name` (die Angabe von ist Pflicht) definiert die zu triggernde Logik. -- `by` Name der aufrufenden Logik -- `source` der eigentliche Grund für das Auslösen der Logik -- `value` Wert (des auslösenden Items) -- `dt` datetime Objekt (berücksichtigt Zeitzone), welches die Trigger-Zeit bestimmt. - - -.. note:: - - Ab SmartHomeNG v1.4 darauf achten, dass eine Trennung der - Namensräume für die Trigger stattgefunden hat. Wenn sh.trigger genutzt wird, muss dem Namen - **logics.** vorangestellt werden. - - Also: statt ```sh.trigger('my_logic')`` --> ```sh.sh.trigger('logics.my_logic')``` - - - -sh.scheduler.change() -^^^^^^^^^^^^^^^^^^^^^ - -Diese Methode ändert einige Laufzeit Optionen der Logik. - -.. code-block:: python - - sh.scheduler.change('alarmclock', active=False) - -disabled die Logik ‘alarmclock’. Außer dem active Flag, ist es möglich `cron` und `cycle` zu ändern. - - - diff --git a/doc/user/source/referenz/logiken/objekteundmethoden_sonne_mond.rst b/doc/user/source/referenz/logiken/objekteundmethoden_sonne_mond.rst deleted file mode 100644 index da05ded838..0000000000 --- a/doc/user/source/referenz/logiken/objekteundmethoden_sonne_mond.rst +++ /dev/null @@ -1,41 +0,0 @@ -Zeit, Sonne und Mond -==================== - - -sh.sun ------- - -Dieses Objekt bietet Zugriff auf Parameter der Sonne. Um dieses Objekt zu verwenden, ist es -erforderlich, den Breitengrad (latitude, z.B. lat: 53.5989481) und den -Längengrad (longitude z.B. lon: 10.0459898), -sowie die Höhe über dem Meeresspiegel (elevation z.B.: elev: 20) -in der Datei **../etc/smarthome.yaml** anzugeben. - -.. code-block:: python - :caption: Beispiele zur Sonnenstandsberechnung - - # sh.sun.pos(offset) hierbei gibt offset die Differenz in Zeit-Minuten zur aktuellen Zeit an - azimut, altitude = sh.sun.pos() # liefert die aktuelle Position der Sonne - azimut, altitude = sh.sun.pos(30) # liefert die Position, welche die Sonne in 30 Minuten haben wird - - # sh.sun.set(offset) hierbei gibt offset die Differenz in Grad zum nächsten Sonnenuntergang an - sunset = sh.sun.set() # liefert den utc-basierten Zeitpunkt des nächsten Sonnenuntergangs - sunset_tw = sh.sun.set(-6) # liefert den utc-basierten Zeitpunkt zu dem die Sonne 6° unter dem Horizont - # steht. (Ende der bürgerlichen Abenddämmerung) - - # sh.sun.rise(offset) hierbei gibt offset die Differenz in Grad zum nächsten Sonnenaufganges an - sunrise = sh.sun.rise() # liefert den utc-basierten Zeitpunkt des nächsten Sonnenaufganges - sunrise_tw = sh.sun.rise(-6) # liefert den utc-basierten Zeitpunkt zu dem die Sonne wieder 6° unter - # dem Horizont steht. (Beginn der nächsten bürgerlichen Morgendämmerung) - - -sh.moon -------- - -Neben den drei Funktionen (sh.moon.pos, sh.moon.set, sh.moon.rise) stehen zwei weitere zur Verfügung. - -**sh.moon.light(offset)** liefert einen Wert von 0 bis 100 der beleuchteten Fläche zur aktuellen Zeit + Offset. -**sh.moon.phase(offset)** gibt die Mondphase als ganze Zahl (0 bis 7) zurück, wobei: 0 = Neumond, 4 = Vollmond, 6 = abnehmender Halbmond - - - diff --git a/doc/user/source/referenz/logiken/objekteundmethoden_tools.rst b/doc/user/source/referenz/logiken/objekteundmethoden_tools.rst deleted file mode 100644 index e13aeaae3b..0000000000 --- a/doc/user/source/referenz/logiken/objekteundmethoden_tools.rst +++ /dev/null @@ -1,76 +0,0 @@ -sh.tools Methoden ------------------ - -Das sh.tools Objekt stellt einige nützliche Funktionen zur Verfügung: - - -sh.tools.ping() -^^^^^^^^^^^^^^^ - -Sendet ein Ping an einen Computer und liefert True zurück, falls der Computer antwortet. - -.. code-block:: python - - sh.office.laptop(sh.tools.ping('hostname')) - -speichert das Ergebnis des Pings von 'hostname' in das Item office.laptop: - - -sh.tools.dewpoint() -^^^^^^^^^^^^^^^^^^^ - -Errechnet der Taupukt für die gegebene Temperatur und Luftfeuchtigkeit. - -.. code-block:: python - - sh.office.dew(sh.tools.dewpoint(sh.office.temp(), sh.office.hum()) - - -sh.tools.fetch_url() -^^^^^^^^^^^^^^^^^^^^ - -Liefert den Inhalt einer Website als String oder ``False``, falls das Lesen der Website fehlschlägt. - - -.. code-block:: python - - sh.tools.fetch_url('https://www.regular.com') - -Es ist möglich ‘username’ und ‘password’ anzugeben, um sich bei der Website zu authentifizieren: - -.. code-block:: python - - sh.tools.fetch_url('https://www.special.com', 'username', 'password') - -Es ist auch möglich das Standard Timeout von 2 Sekunden für den Aufruf zu ändern: - -.. code-block:: python - - sh.tools.fetch_url('https://www.regular.com', timeout=4) - - -sh.tools.dt2ts(dt) -^^^^^^^^^^^^^^^^^^ - -Converts an datetime object to a unix timestamp. - - -sh.tools.dt2js(dt) -^^^^^^^^^^^^^^^^^^ - -Konvertiert ein datetime Objekt zu einem Json Timestamp. - - -sh.tools.rel2abs(temp, hum) -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Konvertiert die relative Luftfeuchtigkeit zur absoluten Luftfeuchtigkeit - - -sh.tools.runtime() -^^^^^^^^^^^^^^^^^^ - -Liefert die Laufzeit von SmartHomeNG zurück. - - - diff --git a/doc/user/source/referenz/logiken/objekteundmethoden_feiertage_datum_zeit.rst b/doc/user/source/referenz/smarthomeng/feiertage_datum_zeit.rst similarity index 100% rename from doc/user/source/referenz/logiken/objekteundmethoden_feiertage_datum_zeit.rst rename to doc/user/source/referenz/smarthomeng/feiertage_datum_zeit.rst diff --git a/doc/user/source/referenz/smarthomeng/methoden_sonne_mond.rst b/doc/user/source/referenz/smarthomeng/methoden_sonne_mond.rst new file mode 100644 index 0000000000..0e03260a48 --- /dev/null +++ b/doc/user/source/referenz/smarthomeng/methoden_sonne_mond.rst @@ -0,0 +1,154 @@ + + +Astronomie (Sonne und Mond) +=========================== + +sh.sun +------ + +Dieses Objekt bietet Zugriff auf Parameter der Sonne. Um dieses Objekt zu verwenden, ist es erforderlich, +den Breitengrad (latitude, z.B. lat: 53.5989481) und den Längengrad (longitude z.B. lon: 10.0459898), +sowie die Höhe über dem Meeresspiegel (elevation z.B.: elev: 20) in der Datei **../etc/smarthome.yaml** anzugeben. + + +sh.sun.pos() +~~~~~~~~~~~~ + +Die Methode ``sh.sun.pos()`` liefert die Position der Sonne und hat zwei optionale Parameter: + +- Der erste Parameter ``offset`` ist der zeitliche Offset zu jetzt in Minuten +- Der zweite Parameter ``degree`` gibt an, ob das Ergebnis im Bogenmaß oder in Grad erfolgen soll. + +.. code-block:: python + :caption: Beispiele zur Sonnenstandsberechnung + + # sh.sun.pos(offset) hierbei gibt offset die Differenz in Zeit-Minuten zur aktuellen Zeit an + + azimut, altitude = sh.sun.pos() # liefert die aktuelle Position der Sonne, Ergbnis im Bogenmaß + azimut, altitude = sh.sun.pos(30) # liefert die Position, welche die Sonne in 30 Minuten haben wird im Bogenmaß + + azimut, altitude = sh.sun.pos(degree=True) # liefert die aktuelle Position der Sonne in Grad + azimut, altitude = sh.sun.pos(-45, True) # liefert die Position, welche die Sonne vor 45 Minuten hatte in Grad + + +sh.sun.set() +~~~~~~~~~~~~ + +Die Methode ``sh.sun.set()`` hat liefert den Zeitpunkt des nächsten Sonnenuntergangs und hat einen optionale Parameter: + +- Der Parameter ``offset`` ist der Offset zum Sonnenuntergang in Grad + (eine negative Zahl gibt an, wieviel Grad die Sonne unter dem Horizont stehen soll) + +Das Ergebnis ist ein auf UTC basierendes ``datetime`` Objekt + +.. code-block:: python + :caption: Beispiele zur Sonnenuntergangsberechnung + + # sh.sun.set(offset) hierbei gibt offset die Differenz in Grad zum nächsten Sonnenuntergang an + + sunset = sh.sun.set() # liefert den utc-basierten Zeitpunkt des nächsten Sonnenuntergangs + sunset_tw = sh.sun.set(-6) # liefert den utc-basierten Zeitpunkt zu dem die Sonne 6° unter dem Horizont + # steht. (Ende der bürgerlichen Abenddämmerung) + + +sh.sun.rise() +~~~~~~~~~~~~~ + +Die Methode ``sh.sun.rise()`` hat liefert den Zeitpunkt des nächsten Sonnenuntergangs und hat einen optionale Parameter: + +- Der Parameter ``offset`` ist der Offset zum Sonnenuntergang in Grad + (eine negative Zahl gibt an, wieviel Grad die Sonne unter dem Horizont stehen soll) + +Das Ergebnis ist ein auf UTC basierendes ``datetime`` Objekt + +.. code-block:: python + :caption: Beispiele zur Sonnenaufgangsberechnung + + # sh.sun.rise(offset) hierbei gibt offset die Differenz in Grad zum nächsten Sonnenaufganges an + + sunrise = sh.sun.rise() # liefert den utc-basierten Zeitpunkt des nächsten Sonnenaufganges + sunrise_tw = sh.sun.rise(-6) # liefert den utc-basierten Zeitpunkt zu dem die Sonne wieder 6° unter + # dem Horizont steht. (Beginn der nächsten bürgerlichen Morgendämmerung) + + +sh.moon +------- + +Neben den drei Funktionen ``pos``, ``set`` und ``rise`` (wie beim Objekt ``sh.sun``) gibt es noch +zwei weitere Funktionen: + +Neben den drei Funktionen ``sh.moon.pos()``, ``sh.moon.set()`` und ``sh.moon.rise()`` (analog zum ``sh.sun`` Objekt) +stehen zwei weitere Funktionen (``sh.moon.light()`` und ``sh.moon.phase()``) zur Verfügung. + + +sh.moon.pos() +~~~~~~~~~~~~~ + +Die Methode ``sh.moon.pos()`` liefert die Position des Mondes und hat zwei optionale Parameter: + +- Der erste Parameter ``offset`` ist der zeitliche Offset zu jetzt in Minuten +- Der zweite Parameter ``degree`` gibt an, ob das Ergebnis im Bogenmaß oder in Grad erfolgen soll. + +.. code-block:: python + :caption: Beispiele zur Mondstandsberechnung + + # sh.moon.pos(offset) hierbei gibt offset die Differenz in Zeit-Minuten zur aktuellen Zeit an + + azimut, altitude = sh.moon.pos() # liefert die aktuelle Position des Mondes, Ergbnis im Bogenmaß + azimut, altitude = sh.moon.pos(30) # liefert die Position, welche der Mond in 30 Minuten haben wird im Bogenmaß + + azimut, altitude = sh.moon.pos(degree=True) # liefert die aktuelle Position des Mondes in Grad + azimut, altitude = sh.moon.pos(-45, True) # liefert die Position, welche der Mond vor 45 Minuten hatte in Grad + + +sh.moon.set() +~~~~~~~~~~~~~ + +Die Methode ``sh.moon.set()`` hat liefert den Zeitpunkt des nächsten Monduntergangs und hat einen optionale Parameter: + +- Der Parameter ``offset`` ist der Offset zum Monduntergang in Grad + (eine negative Zahl gibt an, wieviel Grad der Mond unter dem Horizont stehen soll) + +Das Ergebnis ist ein auf UTC basierendes ``datetime`` Objekt + +.. code-block:: python + :caption: Beispiele zur Monduntergangsberechnung + + # sh.moon.set(offset) hierbei gibt offset die Differenz in Grad zum nächsten Monduntergang an + + moonset = sh.moon.set() # liefert den utc-basierten Zeitpunkt des nächsten Monduntergangs + moonset_tw = sh.moon.set(-6) # liefert den utc-basierten Zeitpunkt zu dem der Mond 6° unter dem Horizont steht + + +sh.moon.rise() +~~~~~~~~~~~~~~ + +Die Methode ``sh.moon.rise()`` hat liefert den Zeitpunkt des nächsten Monduntergangs und hat einen optionale Parameter: + +- Der Parameter ``offset`` ist der Offset zum Monduntergang in Grad + (eine negative Zahl gibt an, wieviel Grad der Mond unter dem Horizont stehen soll) + +Das Ergebnis ist ein auf UTC basierendes ``datetime`` Objekt + +.. code-block:: python + :caption: Beispiele zur Mondaufgangsberechnung + + # sh.moon.rise(offset) hierbei gibt offset die Differenz in Grad zum nächsten Mondaufganges an + + moonrise = sh.moon.rise() # liefert den utc-basierten Zeitpunkt des nächsten Mondaufganges + moonrise_tw = sh.moon.rise(-6) # liefert den utc-basierten Zeitpunkt zu dem der Mond wieder 6° unter + # dem Horizont steht. + + +sh.moon.light() +~~~~~~~~~~~~~~~ + +``sh.moon.light(offset)`` liefert einen Wert von 0 bis 100 der beleuchteten Fläche zur aktuellen Zeit + Offset. + + +sh.moon.phase() +~~~~~~~~~~~~~~~ + +``sh.moon.phase(offset)`` gibt die Mondphase als ganze Zahl (0 bis 7) zurück, +wobei: 0 = Neumond, 4 = Vollmond, 6 = abnehmender Halbmond + From 3526bb58166599ebd0fa78eddc94b3ac3447779a Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 24 Jan 2022 22:00:42 +0100 Subject: [PATCH 45/95] doc: Updated reference section for logics --- doc/user/source/admin/logiken.rst | 10 +- .../source/entwicklung/logiken/logiken.rst | 131 +++++++++++++++++- .../logiken/logiken_grundstruktur.rst | 42 +++--- .../logiken/logiken_plugin_funktionen.rst | 55 +++++++- logics/logic.tpl | 13 -- 5 files changed, 203 insertions(+), 48 deletions(-) diff --git a/doc/user/source/admin/logiken.rst b/doc/user/source/admin/logiken.rst index 5a08c19847..b8e3614c81 100644 --- a/doc/user/source/admin/logiken.rst +++ b/doc/user/source/admin/logiken.rst @@ -10,7 +10,7 @@ Liste der Logiken ================= Unter **Logiken** wird eine Liste der konfigurierten Logiken mit einer Reihe von Informationen angezeigt. -Rechts in der jeweiligen Zeile sind Icons über die +Rechts in der jeweiligen Zeile sind Buttons, über die - eine Logik ausgelöst (getriggert) werden kann, - eine Logik neu geladen werden kann, @@ -18,6 +18,14 @@ Rechts in der jeweiligen Zeile sind Icons über die - eine Logik entladen werden kann, - eine Logik inclusive Code und Parametern gelöscht werden kann +Die Liste der geladenen Logiken ist nach dem Namen der Logik alphabetisch sortiert. + +Nicht geladene Logiken werden am unteren Ende der Liste angezeigt. Für nicht geladene Logiken stehen +nur zwei Buttons zum Auslösen von Aktionen zur Verfügung, über die + +- eine Logik geladen werden kann, +- eine Logik inclusive Code und Parametern gelöscht werden kann + Oberhalb der Liste findet sich ein Button, der die Erstellung einer neuen Logik ermöglicht. Auf einem 2. Tab kann eine Liste der Systemlogiken eingesehen werden. diff --git a/doc/user/source/entwicklung/logiken/logiken.rst b/doc/user/source/entwicklung/logiken/logiken.rst index f7c571d1c4..5733b26ac4 100644 --- a/doc/user/source/entwicklung/logiken/logiken.rst +++ b/doc/user/source/entwicklung/logiken/logiken.rst @@ -7,27 +7,144 @@ .. role:: blacksup +===================================== Logiken :bluesup:`under construction` ===================================== +Eine Logik besteht aus einem Python Skript sowie einer Reihe von Parametern die den Aufruf (das Triggern) der Logik +steuern. Eine Logik kan während der Laufzeit von SmartHomeNG geladen und entladen werden. Dadurch sind Veränderungen +ein einer Logik möglich, ohne SmartHomeNG neu starten zu müssen. + Einführung ----------- +========== + +Das eigentliche Python Skript einer Logik muss im Verzeichnis ``../logics`` der SmartHomeNG Installation abgelegt +werden. Damit die Logik getriggert und ausgeführt wird, muss sie zusätzlich in der Konfigurationsdatei +``../etc/logic.yaml`` konfiguriert werden. Hier wird festgelegt, unter welchen Bedingungen eine bestimmte Logik +auszuführen ist. + +Wenn eine Logik mit der Admin GUI erstellt wurde, ist sie nach dem ersten erfolgreichen Laden pausiert (disabled). +Damit die Logik ausgeführt werden kann, muss sie zuerst mit dem "Play"-Button enabeld werden. (Der "Play"-Button +wechselt dabei zum "Pause"-Button.) + + +Behandlung von Fehlern in der Logik +----------------------------------- + +Syntaktische Fehler +~~~~~~~~~~~~~~~~~~~ + +Wenn eine Logik geladen wird, entweder beim Start von SmartHomeNG oder durch die Admin GUI, wird das Python Skript, +wie ein normales Python Programm, in einen Bytecode übersetzt. Falls hierbei ein grober syntaktischer Fehler auftritt, +wird die Logik nicht geladen und es wird ein entsprechender Fehler geloggt. + +Beispiel: Folgende Test Logik + +.. code-block:: python + + #!/usr/bin/env python3 + # test_logic.py + + logger.warning("Start der Logik") + + Hier steht Müll + + logger.warning("Ende der Logik") + + +erzeugt beim laden/übersetzen folgenden Logeintrag: + +.. code-block:: text + + 2022-01-24 12:51:26 ERROR lib.logic Exception: invalid syntax (test_logic.py, line 6) + Traceback (most recent call last): + File "/usr/local/shng_dev/lib/logic.py", line 1034, in _generate_bytecode + self.bytecode = compile(code, self.pathname, 'exec') + File "/usr/local/shng_dev/logics/test_logic.py", line 6 + Hier steht Müll + ^ + SyntaxError: invalid syntax + + +Laufzeit Fehler +~~~~~~~~~~~~~~~ + +Nachdem eine Logik erfolgreich geladen wurde, können bei der Ausführung der Logik Laufzeit Fehler auftreten. +Falls ein Laufzeitfehler nicht innerhalb der Logik abgefangen wird, führt er zur Beendigung des aktuellen +Laufes der Logik und es wird ein entsprechender Fehler geloggt. Die Logik bleibt jedoch geladen und wird, wenn sie +getriggert wird, erneut ausgeführt. -Logiken in SmartHomeNG sind in Python geschriebene Skript. Sie werden im Unterordner ``logics/`` abgelegt -und definiert in ``etc/logic.yaml``. -Die Konfigurationsdatei ``etc/logic.yaml`` beschreibt für SmartHomeNG unter welchen Bedingungen eine bestimmte Logik auszuführen ist. +Beispiel: Folgende Test Logik -Die Vorlage für das Skript einer Logik kann folgendermaßen aussehen: +.. code-block:: python + #!/usr/bin/env python3 + # test_logic.py + + logger.warning("Start der Logik") + + var = int('abc') + + logger.warning("Ende der Logik") + + +erzeugt zur Laufzeit folgenden Logeintrag: + +.. code-block:: text + + 2022-01-24 13:55:14 ERROR logics.test_logic In der Logik ist ein Fehler aufgetreten: + Logik 'test_logic', Datei '/usr/local/shng_dev/logics/test_logic.py', Zeile 6 + Hauptroutine der Logik, Exception: invalid literal for int() with base 10: 'abc' + + +Template für eine neue Logik +---------------------------- + +Eine neue Logik sollte möglichst auf der folgenden Vorlage ``logic.tpl`` aufbauen. +Die Template Datei ist im Verzeichnis ``logics/`` abgelegt: .. literalinclude:: /logics/logic.tpl - :caption: /logics/example_logic.py + :caption: /logics/logic.tpl :language: python +Um zum Beispiel eine Logik ``example`` zu erzeugen, muss zuerst die Datei ``logics/logic.tpl`` auf die +Datei ``logics/example.py`` kopiert werden und in der erstellten Datei der eigentliche Code der Logik erfasst werden. + +Anschließend muss noch in der Konfigurationsdatei ``etc/logic.yaml`` die Konfiguration für die Logik festgelegt +werden. + +Die Minimalkonfiguration sieht für das Beispiel folgendermaßen aus: + +.. code-block:: yaml + + example: + filename: example.py + +Der Name der Logik wird durch den Namen des Abschnitts ``example`` in der Konfigurationsdatei festgelegt. Unter diesem +Namen wird die Logik innerhalb von SmartHomeNG angesprochen. Der Parameter ``filename`` legt fest, in welcher +Datei der Code für die Logik ``example`` gespeichert ist. Dafür kann im Prinzip ein beliebiger Dateiname gewählt +werden, solange die Extension ``.py`` ist. Um mögliche Verwirrungen zu vermeiden, wird jedoch empfohlen den +Namen der Logik und den Namen der Datei gleich zu wählen. Generell sollten Dateinamen nur aus Kleinbuchstaben +bestehen, da je nach verwendetem Betriebssystem das Dateisystem evtl. Case sensitiv ist und ``Example.py`` eine +andere Datei sein kann als ``example.py``. + +Erst mit der Existenz dieser Konfiguration kann die Logik gestartet werden. Die Logik würde zwar noch nicht durch +ein normaler Ereignis innerhalb von SmartHomeNG getriggert, da keine Trigger definiert sind, die Logik kann jedoch +aus der Admin GUI getriggert werden. + +Wenn die Logik zum Beispiel beim Start von SmartHomeNG getriggert werden soll, so muss noch der folgende ``crontab`` +Parameter angebeben werden: + +.. code-block:: yaml + + example: + filename: example.py + crontab: init -> - - - - - - - - - - - - - - - - - - - - +Beispiele +========= Die folgende Beispielkonfiguration definiert 4 Logiken: diff --git a/doc/user/source/referenz/logiken/logiken_grundstruktur.rst b/doc/user/source/referenz/logiken/logiken_grundstruktur.rst index 35489c46c7..a53959e536 100644 --- a/doc/user/source/referenz/logiken/logiken_grundstruktur.rst +++ b/doc/user/source/referenz/logiken/logiken_grundstruktur.rst @@ -7,22 +7,28 @@ .. index:: Struktur; Logiken .. index:: Logiken; Struktur -======================================================= -Grundstruktur einer Logik :bluesup:`under construction` -======================================================= +========================= +Grundstruktur einer Logik +========================= + +Eine Logik besteht aus einem Python Skript sowie einer Reihe von Parametern die den Aufruf (das Triggern) der Logik +steuern. Eine Logik kan während der Laufzeit von SmartHomeNG geladen und entladen werden. Dadurch sind Veränderungen +ein einer Logik möglich, ohne SmartHomeNG neu starten zu müssen. Das eigentliche Python Skript einer Logik muss im Verzeichnis ``../logics`` der SmartHomeNG Installation abgelegt -werden. Damit die Logiken getriggert und ausgeführt werden, müssen sie zusätzlich in der Konfigurationsdatei +werden. Damit die Logik getriggert und ausgeführt wird, muss sie zusätzlich in der Konfigurationsdatei ``../etc/logic.yaml`` konfiguriert werden. -> - - - - - - - - - - +Zur Erstellung einer neuen Logik sollte die Template Datei ``logics/logic.tpl`` als Vorlage genutzt werden. + +Die einfachste Methode zu Erstellung einer neuen Logik stellt die Verwendung der Admin GUI dar. Dort kann sowohl +das Skript erstellt werden, as auch die Parameter zur Konfiguration erfasst werden. Die Admin GUI ermöglicht auch +im Anschluß das Laden einer neuen Logik. -Das wichtigste Objekt einer Logik ist das Smarthome Objekt ``sh``. -Über dieses Objekt kann auf alle Items, Plugins und Funktionen von SmartHomeNG -zugegriffen werden. -Um den Wert eines Items abzufragen kann zum Beispiel ``sh.area.itemname()`` verwendet werden. -Um dem gleichen Item einen neuen Wert mitzugeben, kann dieser Wert als Argument -übergeben werden ``sh.area.itemname(new\_value)`` +Einfache Logiken bestehen nur aus einer Hauptroutine. Es ist jedoch möglich, Funktionen und Klassen innerhalb einer +Logik zu defninieren, um bei umfangreichen Logiken den Code verständlich und wartbar zu halten. + +Eine einfache Logik, die das Wohnzimmerlicht einschaltet, wenn sie getriggert wird, kann zum Beispiel so aussehen: .. code-block:: python @@ -31,16 +37,6 @@ Um dem gleichen Item einen neuen Wert mitzugeben, kann dieser Wert als Argument if not sh.living_room.light(): sh.living_room.light('on') -Auf Items muß unter Nutzung von Klammern ``()`` zugegridden werden sonst wird eine Ausnahme -erzeugt. - -``sh`` kann genutzt werden um über alle Items durchzulaufen: - -.. code-block:: python - - for item in sh: - print item - for child_item in item: - print child_item - +Auf Items muß unter Nutzung von Klammern ``()`` zugegriffen werden, da es sich um eine Item Methode handelt und +nicht um eine Variable. diff --git a/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst b/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst index 78b9ec2cf1..89f4126c3f 100644 --- a/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst +++ b/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst @@ -9,8 +9,55 @@ .. index:: Logiken; Plgin Funktionen -=========================================================== -Nutzung von Plugin Funktionen :bluesup:`under construction` -=========================================================== +============================= +Nutzung von Plugin Funktionen +============================= + +Eine Reihe von Plugins stellt öffentliche Funktionen zur Verfügung, die in Logiken genutzt werden können. +Diese Funktionen sind in der Konfigurationsdokumentation des jeweiligen Plugins beschrieben. + +Im folgenden wird die Nutzung am Beispiel der Funktion ``groupwrite()`` des **knx** Plugins erläutert. + +Dokumentation der Plugin Funktion(en) +===================================== + +Um die Dokumentation der Funktionen eins Plugins zu finden, bzw. herauszufinden, ob ein Plugin öffentliche +Funktionen anbietet, klickt man in der Navigation der Anwender Dokumentation auf **Plugins** und anschließend auf +den Unterpunkt für den Plugin Typ (für das **knx** Plugin auf **Gateway Plugins**). In der angezeigten Tabelle +von Plugins klickt man anschließend in der Spalte **Plugin** auf den Namen (also **knx**). Dadurch wird die +Konfigurations-Dokuemntation (Parameter, Item Attribute, etc.) des Plugins angezeigt. + +Auf der Seite den Abschnitt **Plugins Functions** suchen. Für das **knx** Plugin sind hier mehrere Funktionen +aufgeführt, unter anderem auch die Funktion ``groupwrite(ga, data, dpt)`` mit der Beschreibung ihrer Parameter. + + +Aufruf der Plugin Funktion(en) +============================== + +Funktionen von Plugins greifen intern auf Daten der laufenden Plugin Instanz zu. Es kann also in SmartHomeNG +mehrere Funktionen gleichen Namens geben, wenn man mehrere Instanzen eines Plugins konfiguriert hat. Deshalb kann +man nicht über den Namen des Plugins auf die Funktion zugreifen, sondern über den Konfiguration-Namen der jeweiligen +Instanz, also den Namen der Abschnitts in der Konfigurationsdatei ``etc/plugin.yaml``. + +Der Aufruf der Funktion ``groupwrite(ga, data, dpt)`` hätte folgenden Sytax: + +.. code-block:: python + + sh..groupwrite(ga, data, dpt) + +Wenn das knx Plugin in der Datei ``etc/plugin.yaml`` folgendermaßen konfiguriert ist: + +.. code-block:: yaml + + local_knxd: + plugin_name: knx + use_project_file: true + +und auf die Gruppenadresse **1/2/1** der 8-Bit Wert (DPT **5**) **123** geschrieben werden soll, lautet der Aufruf +der Pluginfunktion so: + +.. code-block:: python + + sh.local_knxd.groupwrite('1/2/1', 123, 5) + -. . . diff --git a/logics/logic.tpl b/logics/logic.tpl index 7adf056233..280e34fa01 100644 --- a/logics/logic.tpl +++ b/logics/logic.tpl @@ -17,16 +17,3 @@ # cycle = 600 # -logger.debug(f"Trigger: {trigger}") # When debug logging is enabled for this logic, the values which - # the logic is called with, are logged - # - # To enable debug logging for the logic, add a logger to your logging - # configuration in ../etc/logging.yaml - # loggers: - # ... - # logics: - # handlers: [shng_details_file] - # level: WARNING - # - # logics.example_logic: # has to be the name of the logic config, not the logic's Python file - # level: DEBUG From 4d5dae45ae6058fcd7b5d8211a16ebf949ab07ae Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 24 Jan 2022 22:01:34 +0100 Subject: [PATCH 46/95] lib.scheduler: Small modification for executing logics --- lib/scheduler.py | 110 +++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 79 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index ace56cd805..f0809a6506 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -628,9 +628,34 @@ def _worker(self): self._task(name, obj, by, source, dest, value) - def _start_logic_task(self, logic, by, source, dest, value): + def _task(self, name, obj, by, source, dest, value): + threading.current_thread().name = name + logger = logging.getLogger(name) + if obj.__class__.__name__ == 'Logic': + self._execute_logic_task(obj, by, source, dest, value) + + elif obj.__class__.__name__ == 'Item': + try: + if value is not None: + obj(value, caller="Scheduler") + except Exception as e: + logger.exception(f"Item {name} exception: {e}") + + else: # method + try: + if value is None: + obj() + else: + obj(**value) + except Exception as e: + logger.exception(f"Method {name} exception: {e}") + + threading.current_thread().name = 'idle' + + + def _execute_logic_task(self, logic, by, source, dest, value): """ - Start a logic from _task method + Execute a logic from _task method :param logic: :return: @@ -671,7 +696,7 @@ def _start_logic_task(self, logic, by, source, dest, value): try: if logic.enabled: if sh.shng_status['code'] < 20: - logger.warning('Logik ignoriert, SmartHomeNG ist noch nicht vollständig initialisiert') + logger.warning(f"Logik ignoriert, SmartHomeNG ist noch nicht vollständig initialisiert - Logik wurde getriggert durch {trigger}") else: logger.debug(f"Getriggert durch: {trigger}") exec(logic.bytecode) @@ -695,83 +720,10 @@ def _start_logic_task(self, logic, by, source, dest, value): if tb[2] == '': logic_method = 'Hauptroutine der Logik' else: - logic_method = tb[2] + '()' - logger.error(f"In der Logik ist ein Fehler aufgetreten:\n Logik '{logic.name}', Datei '{tb[0]}', Zeile {tb[1]}\n Methode '{logic_method}', Exception: '{e}'") - #logger.exception(f"In der Logik ist ein Fehler aufgetreten:\n Logik '{logic.name}', Datei '{tb[0]}', Zeile {tb[1]}\n Methode '{logic_method}', Exception: '{e}'\n ") + logic_method = 'function ' + tb[2] + '()' + logger.error(f"In der Logik ist ein Fehler aufgetreten:\n Logik '{logic.name}', Datei '{tb[0]}', Zeile {tb[1]}\n {logic_method}, Exception: {e}") + #logger.exception(f"In der Logik ist ein Fehler aufgetreten:\n Logik '{logic.name}', Datei '{tb[0]}', Zeile {tb[1]}\n {logic_method}, Exception: '{e}'\n ") return - def _task(self, name, obj, by, source, dest, value): - threading.current_thread().name = name - logger = logging.getLogger(name) - if obj.__class__.__name__ == 'Logic': - self._start_logic_task(obj, by, source, dest, value) - - # source_details = None - # if isinstance(source, dict): - # source_details = source.get('details', '') - # src = source.get('item', '') - # if src == '': - # # get source ('cron' or 'cycle') - # src = source.get('source', '') - # source = src - # trigger = {'by': by, 'source': source, 'source_details': source_details, 'dest': dest, 'value': value} # noqa - # - # #following variables are assigned to be available during logic execution - # sh = self._sh # noqa - # shtime = self.shtime - # items = self.items - # - # # set the logic environment here (for use within functions in logics): - # logic = obj # noqa - # logic.sh = sh - # logic.logger = logger - # logic.shtime = shtime - # logic.items = items - # logic.trigger_dict = trigger # logic.trigger has naming conflict with method logic.trigger of lib.item - # - # #logics = obj._logics - # logics = logic._logics - # - # if not self.mqtt: - # if _lib_modules_found: - # self.mqtt = Modules.get_instance().get_module('mqtt') - # mqtt = self.mqtt - # logic.mqtt = mqtt - # - # try: - # if logic.enabled: - # #exec(obj.bytecode) - # exec(logic.bytecode) - # # store timestamp of last run - # #obj.set_last_run() - # logic.set_last_run() - # for method in logic.get_method_triggers(): - # try: - # method(logic, by, source, dest) - # except Exception as e: - # logger.exception("Logic: Trigger {} for {} failed: {}".format(method, logic.name, e)) - # except SystemExit: - # # ignore exit() call from logic. - # pass - # except Exception as e: - # tb = sys.exc_info()[2] - # tb = traceback.extract_tb(tb)[-1] - # logger.exception("Logic: {0}, File: {1}, Line: {2}, Method: {3}, Exception: {4}".format(name, tb[0], tb[1], tb[2], e)) - - elif obj.__class__.__name__ == 'Item': - try: - if value is not None: - obj(value, caller="Scheduler") - except Exception as e: - logger.exception("Item {0} exception: {1}".format(name, e)) - else: # method - try: - if value is None: - obj() - else: - obj(**value) - except Exception as e: - logger.exception("Method {0} exception: {1}".format(name, e)) - threading.current_thread().name = 'idle' From f77b5420f31b8815653f74b135aef71868d3b7ca Mon Sep 17 00:00:00 2001 From: msinn Date: Wed, 26 Jan 2022 22:30:03 +0100 Subject: [PATCH 47/95] Documentation updates --- doc/user/source/entwicklung/entwicklung.rst | 2 - .../source/entwicklung/logiken/logiken.rst | 11 +++ doc/user/source/index.rst | 4 +- .../source/{entwicklung => referenz}/APIs.rst | 2 +- .../libraries_plugins_logics.rst | 0 doc/user/source/referenz/logiken/logiken.rst | 2 + .../referenz/logiken/logiken_exceptions.rst | 80 +++++++++++++++++++ .../logiken/logiken_konfiguration.rst | 3 - .../logiken/logiken_plugin_funktionen.rst | 2 +- doc/user/source/referenz/referenz.rst | 2 + 10 files changed, 99 insertions(+), 9 deletions(-) rename doc/user/source/{entwicklung => referenz}/APIs.rst (93%) rename doc/user/source/{entwicklung => referenz}/libraries_plugins_logics.rst (100%) create mode 100644 doc/user/source/referenz/logiken/logiken_exceptions.rst diff --git a/doc/user/source/entwicklung/entwicklung.rst b/doc/user/source/entwicklung/entwicklung.rst index cec244da02..0ef68b964c 100644 --- a/doc/user/source/entwicklung/entwicklung.rst +++ b/doc/user/source/entwicklung/entwicklung.rst @@ -18,8 +18,6 @@ Hier entsteht nach und nach der Teil der Dokumentation, welcher sich mit der Ent logiken/logiken plugins/plugins - APIs - libraries_plugins_logics module/module core/core build_doc diff --git a/doc/user/source/entwicklung/logiken/logiken.rst b/doc/user/source/entwicklung/logiken/logiken.rst index 5733b26ac4..cf43213b9f 100644 --- a/doc/user/source/entwicklung/logiken/logiken.rst +++ b/doc/user/source/entwicklung/logiken/logiken.rst @@ -143,6 +143,17 @@ Parameter angebeben werden: crontab: init +Verlassen einer Logik +--------------------- + +Normalerweise wird der aktuelle Lauf einer Logik dadurch beendet, dass das Ende des Codes der Hauptroutine +erreicht wird. In einigen Fällen kann es jedoch sinnvoll sein, die Logik mitten aus dem Code heraus oder as einer +Funktion heraus zu beenden/zu verlassen. + +Dafür kann eine spezielle Exception ausgelöst werden. Das geschieht mit dem Befehl ``raise LeaveLogic()``. +Details hierzu sind in der :ref:`Referenz ` zu finden. + + Beispiele ========= diff --git a/doc/user/source/index.rst b/doc/user/source/index.rst index 19069cb7fb..7a672571e2 100644 --- a/doc/user/source/index.rst +++ b/doc/user/source/index.rst @@ -52,9 +52,9 @@ oder im `Chat auf gitter.im `_ . beispiele/beispiele.rst tools/tools.rst fehlersuche - faq - referenz/referenz entwicklung/entwicklung + referenz/referenz + faq release/release genindex impressum diff --git a/doc/user/source/entwicklung/APIs.rst b/doc/user/source/referenz/APIs.rst similarity index 93% rename from doc/user/source/entwicklung/APIs.rst rename to doc/user/source/referenz/APIs.rst index 56cc89ec27..62213e9d56 100644 --- a/doc/user/source/entwicklung/APIs.rst +++ b/doc/user/source/referenz/APIs.rst @@ -2,7 +2,7 @@ APIs von SmartHomeNG ==================== Es gibt einige APIs um auf die verschiedenen Objekte (oder Objekt Hierarchien) von SmartHomeNG zuzugreifen. -Diese Programmier Interfaces können nützlich sein, wenn man Plugins entwickelt. +Diese Programmier Interfaces können nützlich sein, wenn man Plugins oder auch Logiken entwickelt. Die APIs sind als Libraries implementiert und im Ordner ../lib gespeichert. Die Beschreibung ihrer Funktionalität ist hier beschrieben: diff --git a/doc/user/source/entwicklung/libraries_plugins_logics.rst b/doc/user/source/referenz/libraries_plugins_logics.rst similarity index 100% rename from doc/user/source/entwicklung/libraries_plugins_logics.rst rename to doc/user/source/referenz/libraries_plugins_logics.rst diff --git a/doc/user/source/referenz/logiken/logiken.rst b/doc/user/source/referenz/logiken/logiken.rst index 7f46b63d4c..4a2fcc37e0 100644 --- a/doc/user/source/referenz/logiken/logiken.rst +++ b/doc/user/source/referenz/logiken/logiken.rst @@ -43,4 +43,6 @@ definiert hat). logiken_items logiken_funktionen logiken_mqtt + logiken_exceptions + diff --git a/doc/user/source/referenz/logiken/logiken_exceptions.rst b/doc/user/source/referenz/logiken/logiken_exceptions.rst new file mode 100644 index 0000000000..aa7f51a3f1 --- /dev/null +++ b/doc/user/source/referenz/logiken/logiken_exceptions.rst @@ -0,0 +1,80 @@ + +.. role:: redsup +.. role:: bluesup +.. role:: greensup +.. role:: blacksup + +.. index:: Struktur; Logiken +.. index:: Logiken; Struktur + +========== +Exceptions +========== + +Laufzeit Fehler +=============== + +Nachdem eine Logik erfolgreich geladen wurde, können bei der Ausführung der Logik Laufzeit Fehler auftreten. +Falls ein Laufzeitfehler nicht innerhalb der Logik abgefangen wird, führt er zur Beendigung des aktuellen +Laufes der Logik und es wird ein entsprechender Fehler geloggt. Die Logik bleibt jedoch geladen und wird, wenn sie +getriggert wird, erneut ausgeführt. + +Beispiel: Folgende Test Logik + +.. code-block:: python + + #!/usr/bin/env python3 + # test_logic.py + + logger.warning("Start der Logik") + + var = int('abc') + + logger.warning("Ende der Logik") + + +erzeugt zur Laufzeit folgenden Logeintrag: + +.. code-block:: text + + 2022-01-24 13:55:14 ERROR logics.test_logic In der Logik ist ein Fehler aufgetreten: + Logik 'test_logic', Datei '/usr/local/shng_dev/logics/test_logic.py', Zeile 6 + Hauptroutine der Logik, Exception: invalid literal for int() with base 10: 'abc' + + +.. index:: Logiken; LeaveLogic +.. _logik_exceptions_leavelogic: + +Exception LeaveLogic +==================== + +Normalerweise wird der aktuelle Lauf einer Logik dadurch beendet, dass das Ende des Codes der Hauptroutine +erreicht wird. + +In einigen Fällen kann es jedoch sinnvoll sein, die Logik mitten aus dem Code heraus oder as einer Funktion heraus +zu beenden/zu verlassen. + +Dafür kann eine spezielle Exception ausgelöst werden. Das geschieht mit dem Befehl ``raise LeaveLogic()``: + +.. code-block:: python + + ... + + if logik_beenden: + raise LeaveLogic() + + ... + +Optional kann der Exception ein Grund als Parameter mitgegeben werden. + +.. code-block:: python + + ... + + if logik_beenden: + raise LeaveLogic('Die Logik sollte beendet werden') + + ... + +Falls ein Grund angegeben wird, erfolgt ein Logeintrag mit dem Level INFO, der das Verlassen und diesen Grund loggt. + diff --git a/doc/user/source/referenz/logiken/logiken_konfiguration.rst b/doc/user/source/referenz/logiken/logiken_konfiguration.rst index 15e81064b8..6ef4bccda5 100644 --- a/doc/user/source/referenz/logiken/logiken_konfiguration.rst +++ b/doc/user/source/referenz/logiken/logiken_konfiguration.rst @@ -10,9 +10,6 @@ Logik Konfiguration =================== -Details zur Konfiguration von Logiken finden sich :doc:`hier <../../konfiguration/logiken>` . - - Zur Konfiguration einer Logik wird in der Datei **../etc/logic.yaml** ein Abschnitt für die Logik angelegt. Unter dem Namen dieses Abschnitts wird die Logik an anderen Stellen referenziert. diff --git a/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst b/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst index 89f4126c3f..ab11a1b05d 100644 --- a/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst +++ b/doc/user/source/referenz/logiken/logiken_plugin_funktionen.rst @@ -7,7 +7,7 @@ .. role:: blacksup -.. index:: Logiken; Plgin Funktionen +.. index:: Logiken; Plugin Funktionen ============================= Nutzung von Plugin Funktionen diff --git a/doc/user/source/referenz/referenz.rst b/doc/user/source/referenz/referenz.rst index f07455efb8..9467820160 100644 --- a/doc/user/source/referenz/referenz.rst +++ b/doc/user/source/referenz/referenz.rst @@ -27,3 +27,5 @@ können. metadata/metadata logging/logging userfunctions/userfunctions + libraries_plugins_logics + APIs From be90e27aafcc1b7fcffecdf2776b1ffe812cc1cb Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Fri, 28 Jan 2022 20:11:54 +0100 Subject: [PATCH 48/95] komplettanleitung: pip upgrade raus, python3-pip in gleiche Zeile wie Rest --- .../komplettanleitung/02_smarthomeng.rst | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst b/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst index a1f346c6dd..722ad3e42f 100644 --- a/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst +++ b/doc/user/source/installation/komplettanleitung/02_smarthomeng.rst @@ -25,16 +25,7 @@ Zunächst müssen einige zusätzlichen Pakete installiert werden: .. code-block:: bash - sudo apt-get -y install dialog python3 python3-dev python3-setuptools unzip git-core build-essential - sudo apt-get install python3-pip - - - Dann sollte evtl. noch Pythons Paketmanager PIP auf den neuesten Stand gebracht werden. Die installierte - Version von PIP sollte jedoch bereits ausreichend sein: - - .. code-block:: bash - - python3 -m pip install --upgrade pip + sudo apt-get -y install dialog python3 python3-dev python3-setuptools unzip git-core build-essential python3-pip SmartHomeNG Quellcode laden @@ -103,7 +94,7 @@ Weitere Python Bibliotheken installieren Dieser Mechanismus sorgt auch dafür, dass Pakete die von später konfigurierten Plugins benötigt werden, automatisch nachinstalliert werden. - .. tab:: SmartHomeNG vor v1.7 + .. tab:: SmartHomeNG vor v1.7 Für den ersten Start müssen noch einige Python Packages nachgeladen werden. Im Unterordner ``requirements`` befindet sich dafür eine Datei ``base.txt``. From e55b8b69c1927cc6deb1fd1540bb27bab12e6606 Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 29 Jan 2022 15:51:35 +0100 Subject: [PATCH 49/95] lib.scheduler: Changed loglevel for LeaveLogic from WARNING to INFO --- lib/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index f0809a6506..0fb06e5975 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -710,7 +710,7 @@ def _execute_logic_task(self, logic, by, source, dest, value): except LeaveLogic as e: # 'LeaveLogic' is no error if str(e) != '': - logger.warning(f"Die Logik '{logic.name}' wurde verlassen. Grund: {e}") + logger.info(f"Die Logik '{logic.name}' wurde verlassen. Grund: {e}") except SystemExit: # ignore exit() call from logic. pass From 5a42ed08ddb951d6319ccf3061cdba43e56069e9 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Sat, 29 Jan 2022 18:11:08 +0100 Subject: [PATCH 50/95] removed obsolete TODOs --- bin/smarthome.py | 3 --- lib/smarthome.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/smarthome.py b/bin/smarthome.py index fef94e7360..d9ec042150 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -211,12 +211,9 @@ def _reload_logics(): lib.backup.make_backup_directories(BASE) -# TODO Morg: need to do this first, so interactive console can resume - hopefully? if args.restart: time.sleep(5) lib.daemon.kill(PIDFILE, 30) -# TODO Morg: add code to adjust MODE setting based on inofficial parameters - pass elif args.interactive: MODE = 'interactive' import code diff --git a/lib/smarthome.py b/lib/smarthome.py index faecfd7a89..9f2d6d6b8b 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -728,7 +728,7 @@ def restart(self, source=''): python_bin = sys.executable if ' ' in python_bin: python_bin = '"'+python_bin+'"' - command = python_bin + ' ' + os.path.join(self._base_dir, 'bin', 'smarthome.py') + command = python_bin + ' ' + os.path.join(self._base_dir, 'bin', 'smarthome.py') + ' -r' self._logger.info("Restart command = '{}'".format(command)) try: p = subprocess.Popen(command, shell=True) From b40de729a3ded952da760c1aa3eb4dcb032678e7 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 30 Jan 2022 13:58:35 +0100 Subject: [PATCH 51/95] lib.scheduler: Bugfix for cycle on item-schedulers; added more details to 'update_by' and 'change_by' on items --- lib/scheduler.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index 0fb06e5975..f7b106fd5d 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -453,6 +453,7 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, cron = None else: cron = _cron + if isinstance(cycle, int): source = {'source': 'cycle1', 'details': cycle} cycle = {cycle: cycle} @@ -479,6 +480,8 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, if not from_smartplugin: name = name +'_'+ obj.__self__.get_instance_name() logger.debug("Scheduler: Name changed by adding plugin instance name to: " + name) + #if name.startswith('items.test.test_trigger.'): + # logger.warning(f"ms: add: name={name}, cycle={cycle}, value={value}") self._scheduler[name] = {'prio': prio, 'obj': obj, 'source': source, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True} if next is None: self._next_time(name, offset) @@ -574,6 +577,11 @@ def _next_time(self, name, offset=None): if job['cycle'] is not None: cycle = list(job['cycle'].keys())[0] #value = job['cycle'][cycle] + # set value only, if it is an item scheduler + if job['obj'].__class__.__name__ == 'Item': + value = job['cycle'][cycle] + #if name.startswith('items.test.test_trigger'): + # logger.warning(f"_next_time ms: (cycle) value={value}, job['cycle']={job['cycle']}, job={job}") if offset is None: offset = cycle next_time = now + datetime.timedelta(seconds=offset) @@ -636,8 +644,13 @@ def _task(self, name, obj, by, source, dest, value): elif obj.__class__.__name__ == 'Item': try: + scheduler_source = source.get('source', '') + #if name.startswith('items.test.test_trigger.'): + # logger.warning(f"ms: _task: name={name}, obj={str(obj)}, by={by}, source={source}, dest={dest}, value={value}") + if scheduler_source != '': + scheduler_source = ' ('+scheduler_source+')' if value is not None: - obj(value, caller="Scheduler") + obj(value, caller=("Scheduler"+scheduler_source)) except Exception as e: logger.exception(f"Item {name} exception: {e}") From db870e6134d4658879c1f6ed48c7b9df83706d1d Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 30 Jan 2022 16:11:32 +0100 Subject: [PATCH 52/95] lib.scheduler: Extended details to 'update_by' and 'change_by' on items --- lib/scheduler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index f7b106fd5d..6da8f85f4c 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -645,10 +645,10 @@ def _task(self, name, obj, by, source, dest, value): elif obj.__class__.__name__ == 'Item': try: scheduler_source = source.get('source', '') - #if name.startswith('items.test.test_trigger.'): - # logger.warning(f"ms: _task: name={name}, obj={str(obj)}, by={by}, source={source}, dest={dest}, value={value}") + if name.startswith('items.test.test_trigger.') or name.startswith('items.test.cron_tab.'): + logger.warning(f"ms: _task: name={name}, obj={str(obj)}, by={by}, source={source}, dest={dest}, value={value}") if scheduler_source != '': - scheduler_source = ' ('+scheduler_source+')' + scheduler_source = ':'+scheduler_source+':'+source.get('details','') if value is not None: obj(value, caller=("Scheduler"+scheduler_source)) except Exception as e: From 4a851a76d7dc3f8a4dcbe668c6329debdd284ce7 Mon Sep 17 00:00:00 2001 From: Bernd Meiners Date: Sun, 30 Jan 2022 20:32:41 +0100 Subject: [PATCH 53/95] core: fix never up errors with ephem and enhance debug message in triggertimes --- lib/orb.py | 522 +++++++++++++++++++++++++------------------- lib/triggertimes.py | 2 +- 2 files changed, 294 insertions(+), 230 deletions(-) diff --git a/lib/orb.py b/lib/orb.py index 1a14fa7637..d51abf8bf9 100644 --- a/lib/orb.py +++ b/lib/orb.py @@ -1,229 +1,293 @@ -#!/usr/bin/env python3 -# -# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab -######################################################################### -# Copyright 2011-2014 Marcus Popp marcus@popp.mx -# -######################################################################### -# This file is part of SmartHomeNG. https://github.com/smarthomeNG// -# -# SmartHomeNG is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SmartHomeNG is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SmartHomeNG. If not, see . -########################################################################## - -import logging -import datetime -import math - -logger = logging.getLogger(__name__) - -try: - import ephem -except ImportError as e: - ephem = None # noqa - - -import dateutil.relativedelta -from dateutil.tz import tzutc - - -class Orb(): - - def __init__(self, orb, lon, lat, elev=False): - """ - :param orb: either 'sun' or 'moon' - :param lon: longitude of observer in degrees - :param lat: latitude of observer in degrees - :param elev: elevation of observer in meters - """ - if ephem is None: - logger.warning("Could not find/use ephem!") - return - self._obs = ephem.Observer() - """ - PyEphem: An `Observer` instance allows to compute the positions of - celestial bodies as seen from a particular position on the Earth's surface. - Following attributes can be set after creation (used defaults are given): - `date` - the moment the `Observer` is created - `lat` - zero degrees latitude - `lon` - zero degrees longitude - `elevation` - 0 meters above sea level - `horizon` - 0 degrees - `epoch` - J2000 - `temp` - 15 degrees Celsius - `pressure` - 1010 mBar -""" - self._obs.long = str(lon) - self._obs.lat = str(lat) - if elev: - self._obs.elevation = float(elev) - if orb == 'sun': - self._orb = ephem.Sun() - elif orb == 'moon': - self._orb = ephem.Moon() - self.phase = self._phase - self.light = self._light - - def _avoid_neverup(self, dt, date_utc, doff): - # avoid NeverUp Error if degree offset is too high - # Get times for noon and midnight - originaldoff = doff - midnight = self.midnight(0, 0, dt=dt) - noon = self.noon(0, 0, dt=dt) - # If the altitudes are calculated from previous or next day, set the correct day for the obeserver query - noon = noon if noon >= date_utc else \ - self.noon(0, 0, dt=date_utc + dateutil.relativedelta.relativedelta(days=1)) - midnight = midnight if midnight >= date_utc else \ - self.midnight(0, 0, dt=date_utc - dateutil.relativedelta.relativedelta(days=1)) - # Get lowest and highest altitudes of the relevant day/night - max_altitude = self.pos(offset=None, degree=True, dt=midnight)[1] if doff <= 0 else \ - self.pos(offset=None, degree=True, dt=noon)[1] - # Set observation date back to original queried date - self._obs.date = date_utc - # Limit degree offset to the highest or lowest possible for the given date - doff = max(doff, max_altitude + 0.00001) if doff < 0 else min(doff, max_altitude - 0.00001) if doff > 0 else doff - if not originaldoff == doff: - logger.info("Had to truncate the degree offset to {} as the sun never goes " - "below/above the given value {}.".format(doff, originaldoff)) - return doff - - def noon(self, doff=0, moff=0, dt=None): - if dt is not None: - self._obs.date = dt - dt.utcoffset() - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - else: - self._obs.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - if not doff == 0: - doff = self._avoid_neverup(dt, date_utc, doff) - self._obs.horizon = str(doff) - next_transit = self._obs.next_transit(self._orb).datetime() - next_transit = next_transit + dateutil.relativedelta.relativedelta(minutes=moff) - return next_transit.replace(tzinfo=tzutc()) - - def midnight(self, doff=0, moff=0, dt=None): - if dt is not None: - self._obs.date = dt - dt.utcoffset() - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - else: - self._obs.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - if not doff == 0: - doff = self._avoid_neverup(dt, date_utc, doff) - self._obs.horizon = str(doff) - next_antitransit = self._obs.next_antitransit(self._orb).datetime() - next_antitransit = next_antitransit + dateutil.relativedelta.relativedelta(minutes=moff) - return next_antitransit.replace(tzinfo=tzutc()) - - def rise(self, doff=0, moff=0, center=True, dt=None): - """ - Computes the rise of either sun or moon - :param doff: degrees offset for the observers horizon - :param moff: minutes offset from time of rise (either before or after) - :param center: if True then the centerpoint of either sun or moon will be considered to make the transit otherwise the upper limb will be considered - :param dt: start time for the search for a rise, if not given the current time will be used - :return: - """ - # workaround if rise is 0.001 seconds in the past - if dt is not None: - self._obs.date = dt - dt.utcoffset() - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - else: - self._obs.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - if not doff == 0: - doff = self._avoid_neverup(dt, date_utc, doff) - self._obs.horizon = str(doff) - if not doff == 0: - next_rising = self._obs.next_rising(self._orb, use_center=center).datetime() - else: - next_rising = self._obs.next_rising(self._orb).datetime() - next_rising = next_rising + dateutil.relativedelta.relativedelta(minutes=moff) - return next_rising.replace(tzinfo=tzutc()) - - def set(self, doff=0, moff=0, center=True, dt=None): - """ - Computes the setting of either sun or moon - :param doff: degrees offset for the observers horizon - :param moff: minutes offset from time of setting (either before or after) - :param center: if True then the centerpoint of either sun or moon will be considered to make the transit otherwise the upper limb will be considered - :param dt: start time for the search for a setting, if not given the current time will be used - :return: - """ - # workaround if set is 0.001 seconds in the past - if dt is not None: - self._obs.date = dt - dt.utcoffset() - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - else: - self._obs.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) - date_utc = (self._obs.date.datetime()).replace(tzinfo=tzutc()) - # avoid NeverUp error - if not doff == 0: - doff = self._avoid_neverup(dt, date_utc, doff) - self._obs.horizon = str(doff) - if not doff == 0: - next_setting = self._obs.next_setting(self._orb, use_center=center).datetime() - else: - next_setting = self._obs.next_setting(self._orb).datetime() - next_setting = next_setting + dateutil.relativedelta.relativedelta(minutes=moff) - return next_setting.replace(tzinfo=tzutc()) - - def pos(self, offset=None, degree=False, dt=None): - """ - Calculates the position of either sun or moon - :param offset: given in minutes, shifts the time of calculation by some minutes back or forth - :param degree: if True: return the position of either sun or moon from the observer as degrees, otherwise as radians - :param dt: time for which the position needs to be calculated - :return: a tuple with azimuth and elevation - """ - if dt is None: - date = datetime.datetime.utcnow() - else: - date = dt.replace(tzinfo=tzutc()) - if offset: - date += dateutil.relativedelta.relativedelta(minutes=offset) - self._obs.date = date - self._orb.compute(self._obs) - if degree: - return (math.degrees(self._orb.az), math.degrees(self._orb.alt)) - else: - return (self._orb.az, self._orb.alt) - - def _light(self, offset=None): - """ - Applies only for moon, returns fraction of lunar surface illuminated when viewed from earth - for the current time plus an offset - :param offset: an offset given in minutes - """ - date = datetime.datetime.utcnow() - if offset: - date += dateutil.relativedelta.relativedelta(minutes=offset) - self._obs.date = date - self._orb.compute(self._obs) - return int(round(self._orb.moon_phase * 100)) - - def _phase(self, offset=None): - """ - Applies only for moon, returns the moon phase related to a cycle of approx. 29.5 days - for the current time plus an offset - :param offset: an offset given in minutes - """ - date = datetime.datetime.utcnow() - cycle = 29.530588861 - if offset: - date += dateutil.relativedelta.relativedelta(minutes=offset) - self._obs.date = date - self._orb.compute(self._obs) - last = ephem.previous_new_moon(self._obs.date) - frac = (self._obs.date - last) / cycle - return int(round(frac * 8)) +#!/usr/bin/env python3 +# +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab +######################################################################### +# Copyright 2011-2014 Marcus Popp marcus@popp.mx +# Copyright 2021-2022 Bernd Meiners Bernd.Meiners@mail.de +######################################################################### +# This file is part of SmartHomeNG. https://github.com/smarthomeNG// +# +# SmartHomeNG is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SmartHomeNG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SmartHomeNG. If not, see . +########################################################################## + +import logging +import datetime +import math + +logger = logging.getLogger(__name__) + +try: + import ephem +except ImportError as e: + ephem = None # noqa + +import dateutil.relativedelta +from dateutil.tz import tzutc + +""" +This library contains a class Orb for calculating sun or moon related events. +Currently it uses ephem for calculation of the sky bound events. +""" + +class Orb(): + """ + Save an observers location and the name of a celestial body for future use + + The Methods internally use PyEphem for computation + + An `Observer` instance allows to compute the positions of + celestial bodies as seen from a particular position on the Earth's surface. + Following attributes can be set after creation (used defaults are given): + + `date` - the moment the `Observer` is created + `lat` - zero degrees latitude + `lon` - zero degrees longitude + `elevation` - 0 meters above sea level + `horizon` - 0 degrees + `epoch` - J2000 + `temp` - 15 degrees Celsius + `pressure` - 1010 mBar + """ + + def __init__(self, orb, lon, lat, elev=False): + """ + Save location and celestial body + + :param orb: either 'sun' or 'moon' + :param lon: longitude of observer in degrees + :param lat: latitude of observer in degrees + :param elev: elevation of observer in meters + """ + if ephem is None: + logger.warning("Could not find/use ephem!") + return + + self.orb = orb + self.lat = lat + self.lon = lon + self.elev = elev + + def get_observer_and_orb(self): + """ + Return a tuple of an instance of an observer with location information + and a celestial body + Both returned objects are uniquely created to prevent errors in computation + + See also this thread at `Stackoverflow `_ + dated back to 2015 where the creator of pyephem writes: + + > Second answer: As long as each thread has its own Moon and Observer objects, it should be able to do its own computations without ruining those of any other threads. + + :return: tuple of observer and celestial body + """ + + observer = ephem.Observer() + # ephem expects lat and lon as strings + observer.long = str(self.lon) + observer.lat = str(self.lat) + if self.elev: + observer.elevation = float(self.elev) + + if self.orb == 'sun': + orb = ephem.Sun() + elif self.orb == 'moon': + orb = ephem.Moon() + self.phase = self._phase + self.light = self._light + + return observer,orb + + def _avoid_neverup(self, dt, date_utc, doff): + """ + When specifying an offset for e.g. a sunset or a sunrise it might well be that the + offset is too high to be ever reached for a specific location and time + Therefore this function will limit this offset and return it to the calling function + + :param dt: starting point for calculation + :type dt: datetime + :param date_utc: a datetime with utc time + :type date_utc: datetime + :param doff: offset in degrees + :type doff: float + :return: corrected offset in degrees + :rtype: float + """ + originaldoff = doff + + # Get times for noon and midnight + midnight = self.midnight(0, 0, dt=dt) + noon = self.noon(0, 0, dt=dt) + + # If the altitudes are calculated from previous or next day, set the correct day for the observer query + noon = noon if noon >= date_utc else \ + self.noon(0, 0, dt=date_utc + dateutil.relativedelta.relativedelta(days=1)) + midnight = midnight if midnight >= date_utc else \ + self.midnight(0, 0, dt=date_utc - dateutil.relativedelta.relativedelta(days=1)) + # Get lowest and highest altitudes of the relevant day/night + max_altitude = self.pos(offset=None, degree=True, dt=midnight)[1] if doff <= 0 else \ + self.pos(offset=None, degree=True, dt=noon)[1] + + # Limit degree offset to the highest or lowest possible for the given date + doff = max(doff, max_altitude + 0.00001) if doff < 0 else min(doff, max_altitude - 0.00001) if doff > 0 else doff + if not originaldoff == doff: + logger.notice(f"offset {originaldoff} truncated to {doff}") + return doff + + def noon(self, doff=0, moff=0, dt=None): + observer, orb = self.get_observer_and_orb() + if dt is not None: + observer.date = dt - dt.utcoffset() + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + else: + observer.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + if not doff == 0: + doff = self._avoid_neverup(dt, date_utc, doff) + observer.horizon = str(doff) + next_transit = observer.next_transit(orb).datetime() + next_transit = next_transit + dateutil.relativedelta.relativedelta(minutes=moff) + next_transit = next_transit.replace(tzinfo=tzutc()) + logger.debug(f"ephem: noon for {self.orb} with doff={doff}, moff={moff}, dt={dt} will be {next_transit}") + return next_transit + + def midnight(self, doff=0, moff=0, dt=None): + observer, orb = self.get_observer_and_orb() + if dt is not None: + observer.date = dt - dt.utcoffset() + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + else: + observer.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + if not doff == 0: + doff = self._avoid_neverup(dt, date_utc, doff) + observer.horizon = str(doff) + next_antitransit = observer.next_antitransit(orb).datetime() + next_antitransit = next_antitransit + dateutil.relativedelta.relativedelta(minutes=moff) + next_antitransit = next_antitransit.replace(tzinfo=tzutc()) + logger.debug(f"ephem: midnight for {self.orb} with doff={doff}, moff={moff}, dt={dt} will be {next_antitransit}") + return next_antitransit + + def rise(self, doff=0, moff=0, center=True, dt=None): + """ + Computes the rise of either sun or moon + :param doff: degrees offset for the observers horizon + :param moff: minutes offset from time of rise (either before or after) + :param center: if True then the centerpoint of either sun or moon will be considered to make the transit otherwise the upper limb will be considered + :param dt: start time for the search for a rise, if not given the current time will be used + :return: + """ + observer, orb = self.get_observer_and_orb() + # workaround if rise is 0.001 seconds in the past + if dt is not None: + observer.date = dt - dt.utcoffset() + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + else: + observer.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + if not doff == 0: + doff = self._avoid_neverup(dt, date_utc, doff) + observer.horizon = str(doff) + if not doff == 0: + next_rising = observer.next_rising(orb, use_center=center).datetime() + else: + next_rising = observer.next_rising(orb).datetime() + next_rising = next_rising + dateutil.relativedelta.relativedelta(minutes=moff) + next_rising = next_rising.replace(tzinfo=tzutc()) + logger.debug(f"ephem: next_rising for {self.orb} with doff={doff}, moff={moff}, center={center}, dt={dt} will be {next_rising}") + return next_rising + + def set(self, doff=0, moff=0, center=True, dt=None): + """ + Computes the setting of either sun or moon + :param doff: degrees offset for the observers horizon + :param moff: minutes offset from time of setting (either before or after) + :param center: if True then the centerpoint of either sun or moon will be considered to make the transit otherwise the upper limb will be considered + :param dt: start time for the search for a setting, if not given the current time will be used + :return: + """ + observer, orb = self.get_observer_and_orb() + # workaround if set is 0.001 seconds in the past + if dt is not None: + observer.date = dt - dt.utcoffset() + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + else: + observer.date = datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(minutes=moff) + dateutil.relativedelta.relativedelta(seconds=2) + date_utc = (observer.date.datetime()).replace(tzinfo=tzutc()) + # avoid NeverUp error + if not doff == 0: + doff = self._avoid_neverup(dt, date_utc, doff) + observer.horizon = str(doff) + if not doff == 0: + next_setting = observer.next_setting(orb, use_center=center).datetime() + else: + next_setting = observer.next_setting(orb).datetime() + next_setting = next_setting + dateutil.relativedelta.relativedelta(minutes=moff) + next_setting = next_setting.replace(tzinfo=tzutc()) + logger.debug(f"ephem: next_setting for {self.orb} with doff={doff}, moff={moff}, center={center}, dt={dt} will be {next_setting}") + return next_setting + + def pos(self, offset=None, degree=False, dt=None): + """ + Calculates the position of either sun or moon + :param offset: given in minutes, shifts the time of calculation by some minutes back or forth + :param degree: if True: return the position of either sun or moon from the observer as degrees, otherwise as radians + :param dt: time for which the position needs to be calculated + :return: a tuple with azimuth and elevation + """ + observer, orb = self.get_observer_and_orb() + if dt is None: + date = datetime.datetime.utcnow() + else: + date = dt.replace(tzinfo=tzutc()) + if offset: + date += dateutil.relativedelta.relativedelta(minutes=offset) + observer.date = date + orb.compute(observer) + if degree: + return (math.degrees(orb.az), math.degrees(orb.alt)) + else: + return (orb.az, orb.alt) + + def _light(self, offset=None): + """ + Applies only for moon, returns fraction of lunar surface illuminated when viewed from earth + for the current time plus an offset + :param offset: an offset given in minutes + """ + observer, orb = self.get_observer_and_orb() + date = datetime.datetime.utcnow() + if offset: + date += dateutil.relativedelta.relativedelta(minutes=offset) + observer.date = date + orb.compute(observer) + light = int(round(orb.moon_phase * 100)) + return light + + def _phase(self, offset=None): + """ + Applies only for moon, returns the moon phase related to a cycle of approx. 29.5 days + for the current time plus an offset + :param offset: an offset given in minutes + """ + observer, orb = self.get_observer_and_orb() + date = datetime.datetime.utcnow() + cycle = 29.530588861 + if offset: + date += dateutil.relativedelta.relativedelta(minutes=offset) + observer.date = date + orb.compute(observer) + last = ephem.previous_new_moon(observer.date) + frac = (observer.date - last) / cycle + phase = int(round(frac * 8)) + return phase diff --git a/lib/triggertimes.py b/lib/triggertimes.py index 1c2ce78d50..1c3b337807 100644 --- a/lib/triggertimes.py +++ b/lib/triggertimes.py @@ -922,7 +922,7 @@ def get_next(self, starttime: datetime): if eventtime.tzinfo == tzutc(): eventtime = eventtime.astimezone(Skytime.sh.shtime.tzinfo()) if starttime > eventtime: - logger.error(f"eventtime {eventtime} is earlier than starttime {starttime} maybe ephem>4.1 with errors") + logger.error(f"eventtime {eventtime} is earlier than starttime {starttime} for {self.event}(doff={self.doff},moff={self.moff}, searchtime={searchtime}); maybe ephem>4.1 with errors") #starttime = starttime.replace(hour=0,minute=0,second=0,microsecond=0)+datetime.timedelta(days=1) continue else: From f03cbba50edaa70aeeafaf26eded758363bbec54 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 30 Jan 2022 20:39:31 +0100 Subject: [PATCH 54/95] lib.scheduler: Removed some development logging --- lib/scheduler.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index 6da8f85f4c..83ddcce5f1 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -480,8 +480,6 @@ def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, if not from_smartplugin: name = name +'_'+ obj.__self__.get_instance_name() logger.debug("Scheduler: Name changed by adding plugin instance name to: " + name) - #if name.startswith('items.test.test_trigger.'): - # logger.warning(f"ms: add: name={name}, cycle={cycle}, value={value}") self._scheduler[name] = {'prio': prio, 'obj': obj, 'source': source, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True} if next is None: self._next_time(name, offset) @@ -580,8 +578,6 @@ def _next_time(self, name, offset=None): # set value only, if it is an item scheduler if job['obj'].__class__.__name__ == 'Item': value = job['cycle'][cycle] - #if name.startswith('items.test.test_trigger'): - # logger.warning(f"_next_time ms: (cycle) value={value}, job['cycle']={job['cycle']}, job={job}") if offset is None: offset = cycle next_time = now + datetime.timedelta(seconds=offset) @@ -645,8 +641,6 @@ def _task(self, name, obj, by, source, dest, value): elif obj.__class__.__name__ == 'Item': try: scheduler_source = source.get('source', '') - if name.startswith('items.test.test_trigger.') or name.startswith('items.test.cron_tab.'): - logger.warning(f"ms: _task: name={name}, obj={str(obj)}, by={by}, source={source}, dest={dest}, value={value}") if scheduler_source != '': scheduler_source = ':'+scheduler_source+':'+source.get('details','') if value is not None: From ecfb25fda976fb0c21a963c7657b47a89d74d442 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 30 Jan 2022 21:45:42 +0100 Subject: [PATCH 55/95] Updated release notes --- doc/user/source/release/1_9_x.rst | 313 ++++++++++++++++++++++++++++++ doc/user/source/release/1_x_x.rst | 185 ------------------ 2 files changed, 313 insertions(+), 185 deletions(-) create mode 100644 doc/user/source/release/1_9_x.rst delete mode 100644 doc/user/source/release/1_x_x.rst diff --git a/doc/user/source/release/1_9_x.rst b/doc/user/source/release/1_9_x.rst new file mode 100644 index 0000000000..6489401725 --- /dev/null +++ b/doc/user/source/release/1_9_x.rst @@ -0,0 +1,313 @@ +============================ +Release 1.9.x - tt. mmm 2022 +============================ + +Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. + +.. note:: + + Diese Release Notes sind ein Arbeitsstand. + + - Berücksichtigt sind Commits im smarthome Repository bis incl. 30. Januar 2022 + (lib.scheduler: Removed some development logging) + - Berücksichtigt sind Commits im plugins Repository bis incl. 30. Januar 2022 + (uzsu plugin: use sun and moon objects of sh and new orb.py) + + +Überblick +========= + +Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.9.x sind im +folgenden in diesen Release Notes beschrieben. + + +Unterstützte Python Versionen +----------------------------- + +Die älteste offiziell unterstützte Python Version für SmartHomeNG Release 1.8 ist Python 3.6. +(Siehe auch *Hard- u. Software Anforderungen* im Abschnitt *Installation* zu unterstützten Python Versionen) + +.. + Das bedeutet nicht unbedingt, dass SmartHomeNG ab Release 1.8 nicht mehr unter älteren Python Versionen läuft, + sondern das SmartHomeNG nicht mehr mit älteren Python Versionen getestet wird und das gemeldete Fehler mit älteren + Python Versionen nicht mehr zu Buxfixen führen. + + Es werden jedoch zunehmend Features eingesetzt, die erst ab Python 3.6 zur Verfügung stehen. + So ist Python 3.6 die minimale Vorraussetzung zur Nutzung des neuen Websocket Moduls. + + +Minimum Python Version +---------------------- + +Die absolute Minimum Python Version in der SmartHomeNG startet wurde auf v3.6 angehoben, da Python 3.5 im +September 2020 End-of-Life (End of security fixes) gegangen ist. Bei einer Neuinstallation wird jedoch empfohlen +auf einer der neueren Python Versionen (3.7 oder 3.8) aufzusetzen. + +.. important:: + + Mit dem kommenden Release 1.8 werden die unterstützten Python Versionen + :doc:`(wie hier beschrieben) ` auf **Python 3.6, 3.7, 3.8** angehoben. Python 3.6 + hat eine Reihe sehr interessanter Features und Verbesserungen gebracht, die dann in SmartHomeNG genutzt + werden können. + + Sollten solche neuen Features in den Core Einzug halten, wird die **Absolute Minimum Python Version** auf 3.6 + angehoben werden. Sollten die Features nur in Plugins genutzt werden, so können nur solche Plugins nicht genutzt + werden, wenn eine ältere Python Version als 3.6 eingesetzt wird. + + +Änderungen am Core +================== + +Updates to the CORE +------------------- + +* Fixed initialization for cases where logging.yaml contains instructions that thow an exception + when SmartHomeNG is not fully configured (requirements installed) +* If logging.yaml gives an exception, logging.yaml.default is used + +* ... + +* Items: + + * ... + +* Logics: + + * ... + +* Libs: + + * lib.item: + + * introduce new property last_trigger(_by/_age) and prev_trigger(_by/_age) and according functions. + This way it is possible to figure out what has triggered the eval expression. The property might be + especially useful inside the eval expression itself but can also be used to figure out what, when + (or if) an eval got triggered (without being updated, because maybe the eval didn't succeed or the + value has not changed) + * Get relative item path: Fix issue where last character of expression part got truncated and therefore + eval expression might get invalid + + * lib.network: + + * Fix for udp server eventloop too late + * Pass timeout parameter + * Add port to connect message + * Fix validate_inet_addr return for invalid ip + * Fix for clean shutdown if not connected + * Added __str__ methods to simplyfy logging + + * lib.orb: + + * Set requirement to ephem max version 4.1.0 because 4.1.3 has a bug or breaking change + * Fix never up errors with ephem + + * lib.scheduler: + + * Trigger dict in logics contains now the source (cron, cycle) for scheduler triggers + * Fixed bug where value wasn't set correctly on cron triggers + * Separated code to start a logic task to is's own method + * Implemented 'raise LeaveLogic()' for logics + * Small modification for executing logics + * Added more details to 'update_by' and 'change_by' on items + + * lib.shpypi: + + * Small fixes + + * lib.smarthome: + + * Change getbasedir for user function to avoid deprecated warning + + * lib.triggertimes: + + * Fix a triggertimes with empty params or wrong parameter set size + * Enhance debug message 'eventtime is earlier than starttime' + + +* Modules: + + * http: + + * Added datatables for use in webinterfaces of plugins + + * websocket: + + * Removed "untested" warning from trigger-logic command + * Loglevel for 'no close frame received or sent' reduced to INFO + +* Plugins: + + * ... + +* tests: + + * ... + + +Änderungen bei Plugins +====================== + +New Plugins +----------- + +For details of the changes of the individual plugins, please refer to the documentation of the respective plugin. + +* : ... + + + +Plugin Updates +-------------- + +* database: + + * Prevent multiple threads for handling database_maxage + +* dlms: + + * Enhance returned result + * Fixed a bug where no response was returned + * Parameter baudrate will be stored to config now + * Change parameter name for speed + +* enocean: + + * Added support for EEP A5_30_01 and A5_30_03, e.g. water leak sensor + * Extended plugin webinterface + * Suggest next free device ID (offset) value during teach-in process + * New feature "enable/disable logging of unknown devices" via plugin.yaml or WebIf + * Added tooltips to WebIf + +* garminconnect: + + * Fix for login issues, using PyPi package with latest version (4.1.22) now. Still not 100% perfect, due + to the package implementation. But at least working again + * Added call to login method before each data retreival to prevent session timeouts + * Removed broken logout + +* gpio: + + * Fix error handling and log on startup + * Slight improvements in webif concerning datatables + +* modbus_tcp: + + * slaveUnit zu Items hinzugefügt + * slaveUnit-Bug _regToRead-key (adress.unit) + * bumped version to 1.0.3 + * Bugfix in web interface + * Fixes in example.yaml + +* openweathermap: + + * Add struct for airpollution + * Bumped to 1.8.4 + * Changed default update cycle to 900 sec to prevent getting locked out by server, if for example + smartVISU is using the same API key + +* philips_tv: + + * Minor improvements for webinterface + +* rpi1wire: + + * Udpdate WebIF Tables with new functions + * Error handling improved + * Udpdate WebIF Tables with new functions + +* smartvisu: + + * Copy info block.html from _templates dir (for sv 3.2 and up) + +* sonos: + + * Fix for use with python 3.9 upwards (Thread::isAlive deprecated) + * Update underlying SoCo library to V_0.25.3 + +* stateengine: + + * Add gitignore to webif/visualizations folder to make sure folder is created and files in folder are ignored + * Minor webif update + +* telegram: + + * Fix web interface (using default template) + * Webif support datatables + * Fix a bug for /info item values + +* uzsu: + + * Use sun and moon objects of sh and new orb.py + +* viessmann: + + * Fixed log call + + +Outdated Plugins +---------------- + +The following plugins were already marked in version v1.6 as *deprecated*. This means that the plugins +are still working, but are not developed further anymore and are removed from the release of SmartHomeNG +in the next release. User of these plugins should switch to corresponding succeeding plugins. + +* System Plugins + + * backend - use the administration interface instead + * sqlite_visu2_8 - switch to the **database** plugin + +* Web Plugins + + * wunderground - the free API is not provided anymore by Wunderground + + +The following plugins are marked as *deprecated* with SmartHomeNG v1.7, because neither user nor tester have been found: + +* Gateway Plugins + + * ecmd + * elro + * iaqstick + * snom + * tellstick + +* Interface Plugins + + * easymeter + * smawb + * vr100 + +* Web Plugins + + * nma + +Moreover, the previous mqtt plugin was renamed to mqtt1 and marked as *deprecated*, because the new mqtt +plugin takes over the functionality. This plugin is based on the mqtt module and the recent core. + + +Retired Plugins +--------------- + +The following plugins have been retired. They had been deprecated in one of the preceding releases of SmartHomeNG. +They have been removed from the plugins repository, but they can still be found on github. Now they reside in +the **plugin_archive** repository from where they can be downloaded if they are still needed. + +* ... + + +Weitere Änderungen +================== + +Tools +----- + +* ... + + +Documentation +------------- + +* Added "updated_by()" function to reference page +* Consolidated logic documentation to reference section + + diff --git a/doc/user/source/release/1_x_x.rst b/doc/user/source/release/1_x_x.rst deleted file mode 100644 index b6ab411e7b..0000000000 --- a/doc/user/source/release/1_x_x.rst +++ /dev/null @@ -1,185 +0,0 @@ -========================== -Release 1.x - tt. mmm 2022 -========================== - -Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. - -.. note:: - - Diese Release Notes sind ein Arbeitsstand. - - - Berücksichtigt sind Commits im smarthome Repository bis incl. ... - (...) - - Berücksichtigt sind Commits im plugins Repository bis incl. ... - (...) - - -Überblick -========= - -Dieses ist neues Release für SmartHomeNG. Die Änderungen gegenüber dem Release v1.9.x sind im -folgenden in diesen Release Notes beschrieben. - - -Unterstützte Python Versionen ------------------------------ - -Die älteste offiziell unterstützte Python Version für SmartHomeNG Release 1.8 ist Python 3.6. -(Siehe auch *Hard- u. Software Anforderungen* im Abschnitt *Installation* zu unterstützten Python Versionen) - -.. - Das bedeutet nicht unbedingt, dass SmartHomeNG ab Release 1.8 nicht mehr unter älteren Python Versionen läuft, - sondern das SmartHomeNG nicht mehr mit älteren Python Versionen getestet wird und das gemeldete Fehler mit älteren - Python Versionen nicht mehr zu Buxfixen führen. - - Es werden jedoch zunehmend Features eingesetzt, die erst ab Python 3.6 zur Verfügung stehen. - So ist Python 3.6 die minimale Vorraussetzung zur Nutzung des neuen Websocket Moduls. - - -Minimum Python Version ----------------------- - -Die absolute Minimum Python Version in der SmartHomeNG startet wurde auf v3.6 angehoben, da Python 3.5 im -September 2020 End-of-Life (End of security fixes) gegangen ist. Bei einer Neuinstallation wird jedoch empfohlen -auf einer der neueren Python Versionen (3.7 oder 3.8) aufzusetzen. - -.. important:: - - Mit dem kommenden Release 1.8 werden die unterstützten Python Versionen - :doc:`(wie hier beschrieben) ` auf **Python 3.6, 3.7, 3.8** angehoben. Python 3.6 - hat eine Reihe sehr interessanter Features und Verbesserungen gebracht, die dann in SmartHomeNG genutzt - werden können. - - Sollten solche neuen Features in den Core Einzug halten, wird die **Absolute Minimum Python Version** auf 3.6 - angehoben werden. Sollten die Features nur in Plugins genutzt werden, so können nur solche Plugins nicht genutzt - werden, wenn eine ältere Python Version als 3.6 eingesetzt wird. - - -Änderungen am Core -================== - -Bugfixes in the CORE --------------------- - -* ... - - -Updates in the CORE -------------------- - -* ... - -* Items: - - * ... - -* Logics: - - * ... - -* Libs: - - * lib. ...: - -* Modules: - - * ...: - - * ... - -* Plugins: - - * ... - -* tests: - - * ... - - -Änderungen bei Plugins -====================== - -New Plugins ------------ - -For details of the changes of the individual plugins, please refer to the documentation of the respective plugin. - -* : ... - - - -Plugin Updates --------------- - -* : - - * ... - - -Outdated Plugins ----------------- - -The following plugins were already marked in version v1.6 as *deprecated*. This means that the plugins -are still working, but are not developed further anymore and are removed from the release of SmartHomeNG -in the next release. User of these plugins should switch to corresponding succeeding plugins. - -* System Plugins - - * backend - use the administration interface instead - * sqlite_visu2_8 - switch to the **database** plugin - -* Web Plugins - - * wunderground - the free API is not provided anymore by Wunderground - - -The following plugins are marked as *deprecated* with SmartHomeNG v1.7, because neither user nor tester have been found: - -* Gateway Plugins - - * ecmd - * elro - * iaqstick - * snom - * tellstick - -* Interface Plugins - - * easymeter - * smawb - * vr100 - -* Web Plugins - - * nma - -Moreover, the previous mqtt plugin was renamed to mqtt1 and marked as *deprecated*, because the new mqtt -plugin takes over the functionality. This plugin is based on the mqtt module and the recent core. - - -Retired Plugins ---------------- - -The following plugins have been retired. They had been deprecated in one of the preceding releases of SmartHomeNG. -They have been removed from the plugins repository, but they can still be found on github. Now they reside in -the **plugin_archive** repository from where they can be downloaded if they are still needed. - -* ... - - -Weitere Änderungen -================== - -Tools ------ - -* ... - - -Documentation -------------- - -* ... -* ... - - From afd4425a7a0e8037733654111511c02f39467357 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Mon, 31 Jan 2022 11:49:16 +0100 Subject: [PATCH 56/95] modify startup/connect logging --- lib/network.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/network.py b/lib/network.py index de355c2a38..f32418b9ac 100644 --- a/lib/network.py +++ b/lib/network.py @@ -596,17 +596,20 @@ def connect(self): :return: False if an error prevented us from launching a connection thread. True if a connection thread has been started. :rtype: bool """ + if self._is_connected: # return false if already connected + self.logger.debug(f'Already connected to {self._host}:{self._port}, ignoring new request') + return False + if self._hostip is None: # return False if no valid ip to connect to self.logger.error(f'No valid IP address to connect to {self._host}:{self._port}') self._is_connected = False return False - if self._is_connected: # return false if already connected - self.logger.error(f'Already connected to {self._host}:{self._port}, ignoring new request') - return False - self.__connect_thread = threading.Thread(target=self._connect_thread_worker, name='TCP_Connect') - self.__connect_thread.daemon = True - self.__connect_thread.start() + if not self.__connect_thread: + self.__connect_thread = threading.Thread(target=self._connect_thread_worker, name='TCP_Connect') + self.__connect_thread.daemon = True + if not self.__running: + self.__connect_thread.start() return True def connected(self): @@ -669,10 +672,10 @@ def _connect_thread_worker(self): Thread worker to handle connection. """ if not self.__connect_threadlock.acquire(blocking=False): - self.logger.warning(f'Connection attempt already in progress for {self._host}:{self._port}, ignoring new request') + self.logger.info(f'Connection attempt already in progress for {self._host}:{self._port}, ignoring new request') return if self._is_connected: - self.logger.error(f'Already connected to {self._host}:{self._port}, ignoring new request') + self.logger.info(f'Already connected to {self._host}:{self._port}, ignoring new request') return self.logger.debug(f'Starting connection cycle for {self._host}:{self._port}') self._connect_counter = 0 From 4ce1cf74f80fc0d6ac50417fcaaccd277601f200 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:33:29 +0100 Subject: [PATCH 57/95] modified __running initialisation --- lib/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index f32418b9ac..774ecdaedd 100644 --- a/lib/network.py +++ b/lib/network.py @@ -560,7 +560,7 @@ def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, self.__connect_threadlock = threading.Lock() self.__receive_thread = None self.__receive_threadlock = threading.Lock() - self.__running = True + self.__running = False # self.logger.setLevel(logging.DEBUG) # Das sollte hier NICHT gesetzt werden, sondern in etc/logging.yaml im Logger lib.network konfiguriert werden! @@ -679,6 +679,7 @@ def _connect_thread_worker(self): return self.logger.debug(f'Starting connection cycle for {self._host}:{self._port}') self._connect_counter = 0 + self.__running = True while self.__running and not self._is_connected: # Try a full connect cycle while not self._is_connected and self._connect_counter < self._connect_retries and self.__running: From 31aad6c00295f6b7d8dc5b0a981eceb9ffbc63c9 Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Mon, 31 Jan 2022 13:32:09 +0100 Subject: [PATCH 58/95] lib.network: added more debug logging, modified connect_on_send behaviour to respect autoreconnect setting --- lib/network.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/network.py b/lib/network.py index 774ecdaedd..b58d27d8a9 100644 --- a/lib/network.py +++ b/lib/network.py @@ -605,6 +605,7 @@ def connect(self): self._is_connected = False return False + self.logger.debug(f'Starting connect to {self._host}:{self._port}') if not self.__connect_thread: self.__connect_thread = threading.Thread(target=self._connect_thread_worker, name='TCP_Connect') self.__connect_thread.daemon = True @@ -637,7 +638,12 @@ def send(self, message): # automatically (re)connect on send attempt if not self._is_connected: - self.connect() + if self._autoreconnect: + self.logger.debug(f'auto(re)connecting to host {self._host} on send attempt, message is {message}') + self.connect() + else: + self.logger.warning(f'trying to send {message}, but not connected to host {self._host} and autoreconnect not set. Aborting.') + return False try: if self._is_connected: @@ -685,7 +691,7 @@ def _connect_thread_worker(self): while not self._is_connected and self._connect_counter < self._connect_retries and self.__running: self._connect() if self._is_connected: - try: + try: self.__connect_threadlock.release() if self._connected_callback: self._connected_callback(self) @@ -735,6 +741,7 @@ def __receive_thread_worker(self): """ Thread worker to handle receiving. """ + self.logger.debug(f'started receive thread for host {self._host}') waitobj = IOWait() waitobj.watch(self._socket, read=True) __buffer = b'' From bf6659c17ed7299dbc8f1717500ee501f1563f4f Mon Sep 17 00:00:00 2001 From: msinn Date: Mon, 31 Jan 2022 17:44:58 +0100 Subject: [PATCH 59/95] Documentation updates --- doc/user/source/referenz/logging/logging.rst | 2 + .../logging/logging_textformatierung.rst | 80 +++++++++++++++++++ .../referenz/logiken/logiken_logic_objekt.rst | 2 +- doc/user/source/release/1_9_x.rst | 3 +- 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 doc/user/source/referenz/logging/logging_textformatierung.rst diff --git a/doc/user/source/referenz/logging/logging.rst b/doc/user/source/referenz/logging/logging.rst index e7b850f8e8..47e2485d9b 100644 --- a/doc/user/source/referenz/logging/logging.rst +++ b/doc/user/source/referenz/logging/logging.rst @@ -26,5 +26,7 @@ Die vollständige zum Python Logging Modul ist unter logging_handler logging_formatter logging_filter + logging_textformatierung + diff --git a/doc/user/source/referenz/logging/logging_textformatierung.rst b/doc/user/source/referenz/logging/logging_textformatierung.rst new file mode 100644 index 0000000000..30de7c2cd8 --- /dev/null +++ b/doc/user/source/referenz/logging/logging_textformatierung.rst @@ -0,0 +1,80 @@ + +.. index:: Referenz; Logging, Text Formatierung +.. Index:: Logging, Text Formatierung; Referenz + +.. role:: bluesup +.. role:: redsup + + +============================== +Text Formatierung beim Logging +============================== + +Bei der Erstellung von Logiken, User Functions oder Plugins ist es sinnvoll bestimmte Vorkommnisse oder Ereignisse +in Logdateien festzuhalten. Die anderen Abschnitte zum Logging in der Referenz befassen sich mit der Konfiguration +des Loggings. Hier folgen einige Vorschläge zum Vorgehen bei der Formatierung der eigentlichen Log Message. + +Die Log Message muss dem Logger als **ein** String übergeben werden. Direkt im Log Aufruf kann man den String zwar +in der folgenden Weise zusammen bauen, das ist jedoch nicht zu empfehlen: + +.. code-block:: python + + menge = 24 + preis = 19.98 + logger.info("Artikelmenge: "+str(menge) + ", Preis: " + str(preis) + ", Gesamt: " + str(menge * preis)) + + +In älteren Python Versionen waren unterschiedliche Methoden zur Formatierung üblich. Die veraltete Methode im +Stil von **printf** und **sprintf**: + +.. code-block:: python + + menge = 24 + preis = 19.98 + logger.info("Artikelmenge: %3d, Preis: %6.2f, Gesamt: %8.2f" % (menge, preis, menge * preis)) + + +Der "pythonische Weg" war in **älteren** Python Versionen die Nutzung von "**format**": + +.. code-block:: python + + menge = 24 + preis = 19.98 + logger.info("Artikelmenge: {}, Preis: {}, Gesamt: {}".format(menge, preis, menge * preis)) + +Auch hier war eine Formatierung der Variableninhalte möglich: + + +.. code-block:: python + + menge = 24 + preis = 19.98 + logger.info("Artikelmenge: {:3d}, Preis: {:6.2f}, Gesamt: {:8.2f}".format(menge, preis, menge * preis)) + + +Seit Python 3.6 gibt es eine neue Möglichkeit der Formatierung, die sogenannten **f-Strings**. Diese Methode erhöht +die Lesbarkeit des Python Codes deutlich, besonders bei Verwendung vieler Variablen in einer Message. + +.. tip:: + + Da inzwischen die von SmartHomeNG unterstützte Python Version >= 3.6 ist, können **f-Strings** uneingeschränkt in + SmartHomeNG genutzt werden. + + Diese Art der Formatierung wird ausdrücklich empfohlen. Es ist jedoch nicht notwendig, in älterem Code alle + älteren Formatierungs-Optionen zu ersetzen. + +.. code-block:: python + + menge = 24 + preis = 19.98 + logger.info(f"Artikelmenge: {menge}, Preis: {preis}, Gesamt: {menge * preis}") + + +Auch hier ist die Formatierung der Variableninhalte möglich: + +.. code-block:: python + + menge = 24 + preis = 19.98 + logger.info(f"Artikelmenge: {menge:3d}, Preis: {preis:6.2f}, Gesamt: {menge * preis:8.2f}") + diff --git a/doc/user/source/referenz/logiken/logiken_logic_objekt.rst b/doc/user/source/referenz/logiken/logiken_logic_objekt.rst index b551f23589..1c2edc5911 100644 --- a/doc/user/source/referenz/logiken/logiken_logic_objekt.rst +++ b/doc/user/source/referenz/logiken/logiken_logic_objekt.rst @@ -118,6 +118,6 @@ Zugriff auf das Logics-API über das logics Objekt: | logics.set_config_section_key() | Setzt den Wert eines Schlüssels für eine angegebene Logik (Abschnitt) permanent in ../etc/logic.yaml | +---------------------------------+---------------------------------------------------------------------------------------------------------+ -Der vollständige Syntax der Methoden kann im Abschnitt :doc:`Entwicklung/APIs von SmartHomeNG ` +Der vollständige Syntax der Methoden kann im Abschnitt :doc:`Entwicklung/APIs von SmartHomeNG ` dem **Logics-API** entnommen werden. diff --git a/doc/user/source/release/1_9_x.rst b/doc/user/source/release/1_9_x.rst index 6489401725..cafe0fc9ae 100644 --- a/doc/user/source/release/1_9_x.rst +++ b/doc/user/source/release/1_9_x.rst @@ -73,7 +73,8 @@ Updates to the CORE * Logics: - * ... + * Logics are now prevented from beeing executed before SmartHomeNG is in Run-Mode (the initialization has finished) + even if triggerd (by e.g. item initialization) during initialization phase * Libs: From 8b472bc89032c945a1b8b11b539d03c7ad70f0cf Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Tue, 1 Feb 2022 09:18:01 +0100 Subject: [PATCH 60/95] lib.network: added autoconnect parameter --- lib/network.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/network.py b/lib/network.py index b58d27d8a9..8ef749b7c2 100644 --- a/lib/network.py +++ b/lib/network.py @@ -514,6 +514,7 @@ def data_received_callback(Tcp_client_instance, message) :param retry_cycle: Time between cycles if :param:autoreconnect is True :param binary: Switch between binary and text mode. Text will be encoded / decoded using encoding parameter. :param terminator: Terminator to use to split received data into chunks (split lines for example). If integer then split into n bytes. Default is None means process chunks as received. + :param autoconnect: automatically connect on send. Copies autoreconnect if None :type host: str :type port: int @@ -524,9 +525,10 @@ def data_received_callback(Tcp_client_instance, message) :type retry_cycle: int :type binary: bool :type terminator: int | bytes | str + :type autoconnect: bool """ - def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, connect_cycle=5, retry_cycle=30, binary=False, terminator=False, timeout=1): + def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, connect_cycle=5, retry_cycle=30, binary=False, terminator=False, timeout=1, autoconnect=None): self.logger = logging.getLogger(__name__) # public properties @@ -537,6 +539,9 @@ def __init__(self, host, port, name=None, autoreconnect=True, connect_retries=5, self._host = host self._port = port self._autoreconnect = autoreconnect + self._autoconnect = autoconnect + if self._autoconnect is None: + self._autoconnect = self._autoreconnect self._is_connected = False self._is_receiving = False self._connect_retries = connect_retries @@ -638,11 +643,11 @@ def send(self, message): # automatically (re)connect on send attempt if not self._is_connected: - if self._autoreconnect: - self.logger.debug(f'auto(re)connecting to host {self._host} on send attempt, message is {message}') + if self._autoconnect: + self.logger.debug(f'autoconnecting to host {self._host} on send attempt, message is {message}') self.connect() else: - self.logger.warning(f'trying to send {message}, but not connected to host {self._host} and autoreconnect not set. Aborting.') ++ self.logger.warning(f'trying to send {message}, but not connected to host {self._host} and autoconnect not active. Aborting.') return False try: From 1798111c5570b3b7a2d1c759ccf18ed18e1e550c Mon Sep 17 00:00:00 2001 From: Morg42 <43153739+Morg42@users.noreply.github.com> Date: Tue, 1 Feb 2022 10:35:53 +0100 Subject: [PATCH 61/95] lib.network: fix typo --- lib/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index 8ef749b7c2..e5bd398572 100644 --- a/lib/network.py +++ b/lib/network.py @@ -647,7 +647,7 @@ def send(self, message): self.logger.debug(f'autoconnecting to host {self._host} on send attempt, message is {message}') self.connect() else: -+ self.logger.warning(f'trying to send {message}, but not connected to host {self._host} and autoconnect not active. Aborting.') + self.logger.warning(f'trying to send {message}, but not connected to host {self._host} and autoconnect not active. Aborting.') return False try: From c6726c4e965996a3a1461b5aac5f08823945cfa2 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 1 Feb 2022 21:21:25 +0100 Subject: [PATCH 62/95] docu: update "webinterface filling" info corresponding to https://github.com/smarthomeNG/smarthome/pull/459 --- .../webinterface_filling_webinterface.rst | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst b/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst index 9fc0e2b688..f0b1280570 100644 --- a/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst +++ b/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst @@ -87,29 +87,29 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: - 3. Folgender Script Code muss zwischen ``{% block pluginscripts %}`` und + 3. Folgender Scriptcode muss zwischen ``{% block pluginscripts %}`` und ``{% endblock pluginscripts %}`` eingefügt werden, um ein Filtern und Sortieren der Tabellen zu ermöglichen. - Der Code ``$('#maintable').DataTable( { "paging": false, fixedHeader: true } );`` + Der Code ``$('#maintable').DataTable( {} );`` muss für jede Tabelle, für die Filtern/Sortieren ermöglicht werden soll, kopiert werden. - Dabei ist sicher zu stellen, dass die ID (#maintable) jeweils richtig angepasst wird: + Dabei ist sicher zu stellen, dass die ID (#maintable) jeweils richtig angepasst wird. + Den entsprechenden ``
      {{ _('Item') }}
      `` Tags sind die entsprechenden ids zu vergeben, außerdem sollte die + CSS Klasse ``display`` hinzugefügt werden. + Beispiel: ``
      ``. .. code-block:: HTML 4. Das Logo oben links auf der Seite wird automatisch durch das Logo des konfigurierten Plugin-Typs ersetzt. Wenn das Webinterface ein eigenes Logo mitbringen soll, muss das entsprechende Bild im Verzeichnis ``webif/static/img`` mit dem Namen ``plugin_logo`` abgelegt sein. Die zulässigen Dateiformate sind **.png**, **.jpg** oder **.svg**. Dabei sollte die Größe der Bilddatei die Größe des angezeigten Logos (derzeit ca. 180x150 Pixel) nicht überschreiten, um unnötige Datenübertragungen zu vermeiden. From 8eac8b64bd488ccfad56126801e66081c59bcfd2 Mon Sep 17 00:00:00 2001 From: msinn Date: Tue, 1 Feb 2022 23:10:46 +0100 Subject: [PATCH 63/95] lib.item: Fix for eval expressions, if crontab attribute is given with an init value, which is to be used in a numerical calculation --- lib/item/item.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/item/item.py b/lib/item/item.py index b48d2701e7..07f74c2d29 100644 --- a/lib/item/item.py +++ b/lib/item/item.py @@ -1284,7 +1284,14 @@ def __run_eval(self, value=None, caller='Eval', source=None, dest=None): self.__last_trigger = self.shtime.now() logger.debug("Item {}: Eval triggered by: {}. Evaluating item with value {}. Eval expression: {}".format(self._path, self.__triggered_by, value, self._eval)) - value = eval(self._eval) + #value = eval(self._eval) + # if contab: init = x is set, x is transfered as a string, for that case re-try eval with x converted to float + try: + value = eval(self._eval) + except: + value = float(value) + value = eval(self._eval) + # ms end except Exception as e: # adding "None" as the "destination" information at end of triggered_by # This helps figuring out whether an eval expression was successfully evaluated or not. From 8c8d324e41198df23fb2b60a6439b839824432ef Mon Sep 17 00:00:00 2001 From: msinn Date: Tue, 1 Feb 2022 23:12:11 +0100 Subject: [PATCH 64/95] modules.admin: eval syntax checker sets 'value' to the 'path relative to' item, if it is specified --- modules/admin/api_services.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/admin/api_services.py b/modules/admin/api_services.py index 219c002bb4..5c8db9403d 100755 --- a/modules/admin/api_services.py +++ b/modules/admin/api_services.py @@ -113,6 +113,7 @@ def eval_syntax_checker(self, eval_code, relative_to): if rel_to_item is not None: expanded_code = rel_to_item.get_stringwithabsolutepathes(eval_code, 'sh.', '(') expanded_code = rel_to_item.get_stringwithabsolutepathes(expanded_code, 'sh.', '.property') + value = rel_to_item() # item value for use in eval else: expanded_code = "Error: Item {} does not exist!".format(relative_to) try: From e42e5f02a844c434cd5421799538d2172a345c09 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Wed, 2 Feb 2022 04:09:57 +0100 Subject: [PATCH 65/95] datatables: fix table width in tabs --- .../gstatic/datatables/datatables.defaults.js | 33 +++++++++++-------- .../http/webif/gtemplates/base_plugin.html | 4 +-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/modules/http/webif/gstatic/datatables/datatables.defaults.js b/modules/http/webif/gstatic/datatables/datatables.defaults.js index e44b94a734..c1a24488b4 100644 --- a/modules/http/webif/gstatic/datatables/datatables.defaults.js +++ b/modules/http/webif/gstatic/datatables/datatables.defaults.js @@ -24,23 +24,28 @@ $(window).bind('datatables_defaults', function() { { // Set datatable useful defaults $.extend( $.fn.dataTable.defaults, { - lengthMenu: [ [10, 25, 50, -1], [10, 25, 50, "All"] ], // pagination menu - pageLength: -1, // default to "all" - pagingType: "full_numbers", // include first and last in pagination menu - colReorder: true, // enable colomn reorder by drag and drop - fixedHeader: {header: true, // header will always be visible on top of page when scrolling - headerOffset: $('#webif-navbar').outerHeight() + $('#webif-tabs').outerHeight()}, - responsive: true, // enable responsive extension. - "fnDrawCallback": function(oSettings) { // hide pagination if not needed - if (oSettings._iDisplayLength > oSettings.fnRecordsDisplay() || oSettings._iDisplayLength == -1) { - $(oSettings.nTableWrapper).find('.dataTables_paginate').hide(); - } else { - $(oSettings.nTableWrapper).find('.dataTables_paginate').show(); - } - } + lengthMenu: [ [10, 25, 50, -1], [10, 25, 50, "All"] ], // pagination menu + pageLength: -1, // default to "all" + pagingType: "full_numbers", // include first and last in pagination menu + colReorder: true, // enable colomn reorder by drag and drop + fixedHeader: {header: true, // header will always be visible on top of page when scrolling + headerOffset: $('#webif-navbar').outerHeight() + $('#webif-tabs').outerHeight()}, + responsive: true, // enable responsive extension. + autoWidth: false, + "fnDrawCallback": function(oSettings) { // hide pagination if not needed + if (oSettings._iDisplayLength > oSettings.fnRecordsDisplay() || oSettings._iDisplayLength == -1) { + $(oSettings.nTableWrapper).find('.dataTables_paginate').hide(); + } else { + $(oSettings.nTableWrapper).find('.dataTables_paginate').show(); + } + } }); // Set date format for correct sorting of columns containing date strings $.fn.dataTable.moment('DD.MM.YYYY HH:mm:ss'); + $('a[data-toggle="tab"]').on('shown.bs.tab', function(e){ + $.fn.dataTable.tables({ visible: true, api: true }).columns.adjust().draw(); + $.fn.dataTable.tables({ visible: true, api: true }).responsive.recalc(); + }); } catch (e) { diff --git a/modules/http/webif/gtemplates/base_plugin.html b/modules/http/webif/gtemplates/base_plugin.html index f0b83ee70d..089bc115cb 100755 --- a/modules/http/webif/gtemplates/base_plugin.html +++ b/modules/http/webif/gtemplates/base_plugin.html @@ -19,7 +19,7 @@
      {% endif %} -
      +
      @@ -57,7 +57,7 @@
      {{ _('Plugin') }}     : {% if p.alive %}{{_('Aktiv')}}{% else %}{{ {% block headtable %} {% endblock %} -
      +
      ").insertAfter(H)); -r.nTBody=ea[0];H=t.children("tfoot");0===H.length&&0").appendTo(t));0===H.length||0===H.children().length?t.addClass(C.sNoFooter):0/g,uc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,vc=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,rb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,Z=function(a){return a&&!0!==a&&"-"!==a?!1:!0},hc=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},ic=function(a,b){sb[b]||(sb[b]=new RegExp(ib(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g, -"").replace(sb[b],"."):a},tb=function(a,b,c){var d="string"===typeof a;if(Z(a))return!0;b&&d&&(a=ic(a,b));c&&d&&(a=a.replace(rb,""));return!isNaN(parseFloat(a))&&isFinite(a)},jc=function(a,b,c){return Z(a)?!0:Z(a)||"string"===typeof a?tb(a.replace(Ua,""),b,c)?!0:null:null},U=function(a,b,c){var d=[],e=0,h=a.length;if(c!==q)for(;ea.length)){var b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d")[0],sc=Qa.textContent!==q,tc=/<.*?>/g,gb=u.util.throttle,nc=[],N=Array.prototype,wc=function(a){var b,c=u.settings,d=l.map(c,function(h,f){return h.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e=l.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=l(a):a instanceof l&&(b=a)}else return[];if(b)return b.map(function(h){e= -l.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var B=function(a,b){if(!(this instanceof B))return new B(a,b);var c=[],d=function(f){(f=wc(f))&&c.push.apply(c,f)};if(Array.isArray(a))for(var e=0,h=a.length;ea?new B(b[a],this[a]):null},filter:function(a){var b=[];if(N.filter)b=N.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(g),l("td",k).addClass(g).html(f)[0].colSpan= -oa(a),e.push(k[0]))};h(c,d);b._details&&b._details.detach();b._details=l(e);b._detailsShow&&b._details.insertAfter(b.nTr)},xb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details=q,l(a.nTr).removeClass("dt-hasChild"),qa(c[0]))},qc=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];d._details&&((d._detailsShow=b)?(d._details.insertAfter(d.nTr),l(d.nTr).addClass("dt-hasChild")):(d._details.detach(), -l(d.nTr).removeClass("dt-hasChild")),F(c[0],null,"childRow",[b,a.row(a[0])]),zc(c[0]),qa(c[0]))}},zc=function(a){var b=new B(a),c=a.aoData;b.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0g){var n=l.map(d,function(p,t){return p.bVisible?t:null});return[n[n.length+g]]}return[ua(a,g)];case "name":return l.map(e,function(p,t){return p===m[1]?t:null});default:return[]}if(f.nodeName&&f._DT_CellIndex)return[f._DT_CellIndex.column]; -g=l(h).filter(f).map(function(){return l.inArray(this,h)}).toArray();if(g.length||!f.nodeName)return g;g=l(f).closest("*[data-dt-column]");return g.length?[g.data("dt-column")]:[]},a,c)};y("columns()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=vb(b);var c=this.iterator("table",function(d){return Bc(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh}, -1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",rc,1)});J("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,h){return Ea(b.aoData,h,"search"=== -a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return Ea(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(e,h){if(a===q)return e.aoColumns[h].bVisible;var f=e.aoColumns,g=f[h],k=e.aoData,m;if(a!==q&&g.bVisible!==a){if(a){var n=l.inArray(!0,U(f,"bVisible"),h+1);f=0;for(m=k.length;fd;return!0};u.isDataTable=u.fnIsDataTable=function(a){var b= -l(a).get(0),c=!1;if(a instanceof u.Api)return!0;l.each(u.settings,function(d,e){d=e.nScrollHead?l("table",e.nScrollHead)[0]:null;var h=e.nScrollFoot?l("table",e.nScrollFoot)[0]:null;if(e.nTable===b||d===b||h===b)c=!0});return c};u.tables=u.fnTables=function(a){var b=!1;l.isPlainObject(a)&&(b=a.api,a=a.visible);var c=l.map(u.settings,function(d){if(!a||a&&l(d.nTable).is(":visible"))return d.nTable});return b?new B(c):c};u.camelToHungarian=P;y("$()",function(a,b){b=this.rows(b).nodes();b=l(b);return l([].concat(b.filter(a).toArray(), -b.find(a).toArray()))});l.each(["on","one","off"],function(a,b){y(b+"()",function(){var c=Array.prototype.slice.call(arguments);c[0]=l.map(c[0].split(/\s/),function(e){return e.match(/\.dt\b/)?e:e+".dt"}).join(" ");var d=l(this.tables().nodes());d[b].apply(d,c);return this})});y("clear()",function(){return this.iterator("table",function(a){Ka(a)})});y("settings()",function(){return new B(this.context,this.context)});y("init()",function(){var a=this.context;return a.length?a[0].oInit:null});y("data()", -function(){return this.iterator("table",function(a){return U(a.aoData,"_aData")}).flatten()});y("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,h=b.nTBody,f=b.nTHead,g=b.nTFoot,k=l(e);h=l(h);var m=l(b.nTableWrapper),n=l.map(b.aoData,function(t){return t.nTr}),p;b.bDestroying=!0;F(b,"aoDestroyCallback","destroy",[b]);a||(new B(b)).columns().visible(!0);m.off(".DT").find(":not(tbody *)").off(".DT");l(z).off(".DT-"+b.sInstance); -e!=f.parentNode&&(k.children("thead").detach(),k.append(f));g&&e!=g.parentNode&&(k.children("tfoot").detach(),k.append(g));b.aaSorting=[];b.aaSortingFixed=[];Ra(b);l(n).removeClass(b.asStripeClasses.join(" "));l("th, td",f).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);h.children().detach();h.append(n);f=a?"remove":"detach";k[f]();m[f]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),k.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&& -h.children().each(function(t){l(this).addClass(b.asDestroyStripes[t%p])}));c=l.inArray(b,u.settings);-1!==c&&u.settings.splice(c,1)})});l.each(["column","row","cell"],function(a,b){y(b+"s().every()",function(c){var d=this.selector.opts,e=this;return this.iterator(b,function(h,f,g,k,m){c.call(e[b](f,"cell"===b?g:d,"cell"===b?d:q),f,g,k,m)})})});y("i18n()",function(a,b,c){var d=this.context[0];a=na(a)(d.oLanguage);a===q&&(a=b);c!==q&&l.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)}); -u.version="1.11.3";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,"return":!1};u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};u.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null, -sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};u.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1, -bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}}, -fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last", -sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:l.extend({},u.models.oSearch),sAjaxDataProp:"data", -sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};E(u.defaults);u.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};E(u.defaults.column);u.models.oSettings= -{oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{}, -aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0, -aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:q,oAjaxData:q,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==Q(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length}, -fnRecordsDisplay:function(){return"ssp"==Q(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,h=e.bPaginate;return e.bServerSide?!1===h||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!h||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=M={buttons:{},classes:{},build:"dt/dt-1.11.3/cr-1.5.5/fh-3.2.1/r-2.2.9", -errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:u.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:u.version};l.extend(M,{afnFiltering:M.search,aTypes:M.type.detect,ofnSearch:M.type.search,oSort:M.type.order,afnSortData:M.order,aoFeatures:M.feature,oApi:M.internal,oStdClasses:M.classes,oPagination:M.pager});l.extend(u.ext.classes, -{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled", -sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ec= -u.ext.pager;l.extend(ec,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Da(a,b)]},simple_numbers:function(a,b){return["previous",Da(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Da(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Da(a,b),"last"]},_numbers:Da,numbers_length:7});l.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,h){var f=a.oClasses,g=a.oLanguage.oPaginate, -k=a.oLanguage.oAria.paginate||{},m,n,p=0,t=function(x,w){var r,C=f.sPageButtonDisabled,G=function(I){lb(a,I.data.action,!0)};var aa=0;for(r=w.length;aa").appendTo(x);t(O,L)}else{m=null;n=L;O=a.iTabIndex;switch(L){case "ellipsis":x.append('');break;case "first":m=g.sFirst;0===e&&(O=-1,n+=" "+C);break;case "previous":m=g.sPrevious;0===e&&(O=-1,n+=" "+C);break;case "next":m=g.sNext;if(0=== -h||e===h-1)O=-1,n+=" "+C;break;case "last":m=g.sLast;if(0===h||e===h-1)O=-1,n+=" "+C;break;default:m=a.fnFormatNumber(L+1),n=e===L?f.sPageButtonActive:""}null!==m&&(O=l("",{"class":f.sPageButton+" "+n,"aria-controls":a.sTableId,"aria-label":k[L],"data-dt-idx":p,tabindex:O,id:0===c&&"string"===typeof L?a.sTableId+"_"+L:null}).html(m).appendTo(x),ob(O,{action:L},G),p++)}}};try{var v=l(b).find(A.activeElement).data("dt-idx")}catch(x){}t(l(b).empty(),d);v!==q&&l(b).find("[data-dt-idx="+v+"]").trigger("focus")}}}); -l.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return tb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!uc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||Z(a)?"date":null},function(a,b){b=b.oLanguage.sDecimal;return tb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return Z(a)||"string"===typeof a&&-1!==a.indexOf("<")? -"html":null}]);l.extend(u.ext.type.search,{html:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," ").replace(Ua,""):""},string:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," "):a}});var Ta=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=ic(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};l.extend(M.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return Z(a)?"":a.replace?a.replace(/<.*?>/g, -"").toLowerCase():a+""},"string-pre":function(a){return Z(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return ab?-1:0}});Wa("");l.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c, -d){l("
      ").addClass(d.sSortJUIWrapper).append(b.contents()).append(l("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==g[e]?d.sSortJUIAsc:"desc"== -g[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var yb=function(a){Array.isArray(a)&&(a=a.join(","));return"string"===typeof a?a.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):a};u.render={number:function(a,b,c,d,e){return{display:function(h){if("number"!==typeof h&&"string"!==typeof h)return h;var f=0>h?"-":"",g=parseFloat(h);if(isNaN(g))return yb(h);g=g.toFixed(c);h=Math.abs(g);g=parseInt(h,10);h=c?b+(h-g).toFixed(c).substring(2):"";0===g&&0===parseFloat(h)&& -(f="");return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+h+(e||"")}}},text:function(){return{display:yb,filter:yb}}};l.extend(u.ext.internal,{_fnExternApiFunc:fc,_fnBuildAjax:Oa,_fnAjaxUpdate:Gb,_fnAjaxParameters:Pb,_fnAjaxUpdateDraw:Qb,_fnAjaxDataSrc:Aa,_fnAddColumn:Xa,_fnColumnOptions:Ga,_fnAdjustColumnSizing:ta,_fnVisibleToColumnIndex:ua,_fnColumnIndexToVisible:va,_fnVisbleColumns:oa,_fnGetColumns:Ia,_fnColumnTypes:Za,_fnApplyColumnDefs:Db,_fnHungarianMap:E,_fnCamelToHungarian:P, -_fnLanguageCompat:ma,_fnBrowserDetect:Bb,_fnAddData:ia,_fnAddTr:Ja,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==q?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return l.inArray(c,a.aoData[b].anCells)},_fnGetCellData:T,_fnSetCellData:Eb,_fnSplitObjNotation:bb,_fnGetObjectDataFn:na,_fnSetObjectDataFn:ha,_fnGetDataMaster:cb,_fnClearTable:Ka,_fnDeleteIndex:La,_fnInvalidate:wa,_fnGetRowElements:ab,_fnCreateTr:$a,_fnBuildHead:Fb,_fnDrawHead:ya,_fnDraw:ja,_fnReDraw:ka,_fnAddOptionsHtml:Ib, -_fnDetectHeader:xa,_fnGetUniqueThs:Na,_fnFeatureHtmlFilter:Kb,_fnFilterComplete:za,_fnFilterCustom:Tb,_fnFilterColumn:Sb,_fnFilter:Rb,_fnFilterCreateSearch:hb,_fnEscapeRegex:ib,_fnFilterData:Ub,_fnFeatureHtmlInfo:Nb,_fnUpdateInfo:Xb,_fnInfoMacros:Yb,_fnInitialise:Ba,_fnInitComplete:Pa,_fnLengthChange:jb,_fnFeatureHtmlLength:Jb,_fnFeatureHtmlPaginate:Ob,_fnPageChange:lb,_fnFeatureHtmlProcessing:Lb,_fnProcessingDisplay:V,_fnFeatureHtmlTable:Mb,_fnScrollDraw:Ha,_fnApplyToChildren:ca,_fnCalculateColumnWidths:Ya, -_fnThrottle:gb,_fnConvertToWidth:Zb,_fnGetWidestNode:$b,_fnGetMaxLenString:ac,_fnStringToCss:K,_fnSortFlatten:pa,_fnSort:Hb,_fnSortAria:cc,_fnSortListener:nb,_fnSortAttachListener:eb,_fnSortingClasses:Ra,_fnSortData:bc,_fnSaveState:qa,_fnLoadState:dc,_fnImplementState:pb,_fnSettingsFromNode:Sa,_fnLog:da,_fnMap:X,_fnBindAction:ob,_fnCallbackReg:R,_fnCallbackFire:F,_fnLengthOverflow:kb,_fnRenderer:fb,_fnDataSource:Q,_fnRowAttributes:db,_fnExtend:qb,_fnCalculateEnd:function(){}});l.fn.dataTable=u;u.$= -l;l.fn.dataTableSettings=u.settings;l.fn.dataTableExt=u.ext;l.fn.DataTable=function(a){return l(this).dataTable(a).api()};l.each(u,function(a,b){l.fn.DataTable[a]=b});return u}); +"hidden";D.style.width=O[W]},fa);G&&ca(function(D,W){D.innerHTML='
      '+ea[W]+"
      ";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=I[W]},ba);Math.round(w.outerWidth())g.offsetHeight||"scroll"==n.css("overflow-y")?h+b:h,L&&(g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(la-b)),""!==c&&""===d||da(a,1,"Possible column misalignment",6)):la="100%";p.width=K(la);f.width=K(la); +G&&(a.nScrollFoot.style.width=K(la));!e&&L&&(p.height=K(r.offsetHeight+b));c=w.outerWidth();m[0].style.width=K(c);k.width=K(c);d=w.height()>g.clientHeight||"scroll"==n.css("overflow-y");e="padding"+(aa.bScrollbarLeft?"Left":"Right");k[e]=d?b+"px":"0px";G&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");w.children("colgroup").insertBefore(w.children("thead"));n.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(g.scrollTop=0)}}function ca(a,b,c){for(var d=0,e=0,h= +b.length,f,g;e").appendTo(g.find("tbody"));g.find("thead, tfoot").remove();g.append(l(a.nTHead).clone()).append(l(a.nTFoot).clone());g.find("tfoot th, tfoot td").css("width","");m=Na(a,g.find("thead")[0]);for(v=0;v").css({width:w.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v=0;v").css(h||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(g).appendTo(p);h&&f?g.width(f):h?(g.css("width","auto"),g.removeAttr("width"),g.width()").css("width",K(a)).appendTo(b||A.body);b=a[0].offsetWidth;a.remove();return b}function $b(a,b){var c= +ac(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]:l("
      ").insertAfter(H));r.nTBody=ea[0];H=t.children("tfoot");0===H.length&&0").appendTo(t));0===H.length||0===H.children().length?t.addClass(C.sNoFooter):0/g,uc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,vc=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,rb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,Z=function(a){return a&&!0!==a&&"-"!==a?!1:!0},hc= +function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},ic=function(a,b){sb[b]||(sb[b]=new RegExp(jb(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(sb[b],"."):a},tb=function(a,b,c){var d="string"===typeof a;if(Z(a))return!0;b&&d&&(a=ic(a,b));c&&d&&(a=a.replace(rb,""));return!isNaN(parseFloat(a))&&isFinite(a)},jc=function(a,b,c){return Z(a)?!0:Z(a)||"string"===typeof a?tb(a.replace(Va,""),b,c)?!0:null:null},U=function(a,b,c){var d=[],e=0,h=a.length;if(c!==q)for(;e< +h;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;ea.length)){var b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d< +e;d++){if(b[d]===c){b=!1;break a}c=b[d]}}b=!0}if(b)return a.slice();b=[];e=a.length;var h,f=0;d=0;a:for(;d")[0],sc=Qa.textContent!==q,tc=/<.*?>/g,hb=u.util.throttle,nc=[],N=Array.prototype,wc=function(a){var b,c=u.settings,d=l.map(c,function(h,f){return h.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e= +l.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=l(a):a instanceof l&&(b=a)}else return[];if(b)return b.map(function(h){e=l.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var B=function(a,b){if(!(this instanceof B))return new B(a,b);var c=[],d=function(f){(f=wc(f))&&c.push.apply(c,f)};if(Array.isArray(a))for(var e=0,h=a.length;ea?new B(b[a],this[a]):null},filter:function(a){var b=[];if(N.filter)b=N.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(g),l("td",k).addClass(g).html(f)[0].colSpan=oa(a),e.push(k[0]))};h(c,d);b._details&&b._details.detach();b._details=l(e);b._detailsShow&&b._details.insertAfter(b.nTr)},xb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details= +q,l(a.nTr).removeClass("dt-hasChild"),qa(c[0]))},qc=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];d._details&&((d._detailsShow=b)?(d._details.insertAfter(d.nTr),l(d.nTr).addClass("dt-hasChild")):(d._details.detach(),l(d.nTr).removeClass("dt-hasChild")),F(c[0],null,"childRow",[b,a.row(a[0])]),zc(c[0]),qa(c[0]))}},zc=function(a){var b=new B(a),c=a.aoData;b.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0g){var n=l.map(d,function(p,t){return p.bVisible?t:null});return[n[n.length+g]]}return[ua(a,g)];case "name":return l.map(e,function(p,t){return p===m[1]?t:null});default:return[]}if(f.nodeName&&f._DT_CellIndex)return[f._DT_CellIndex.column];g=l(h).filter(f).map(function(){return l.inArray(this,h)}).toArray();if(g.length||!f.nodeName)return g;g=l(f).closest("*[data-dt-column]");return g.length?[g.data("dt-column")]:[]},a,c)};y("columns()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a, +a="");b=vb(b);var c=this.iterator("table",function(d){return Bc(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh},1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",rc,1)});J("columns().dataSrc()", +"column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,h){return Ea(b.aoData,h,"search"===a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return Ea(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c= +this,d=this.iterator("column",function(e,h){if(a===q)return e.aoColumns[h].bVisible;var f=e.aoColumns,g=f[h],k=e.aoData,m;if(a!==q&&g.bVisible!==a){if(a){var n=l.inArray(!0,U(f,"bVisible"),h+1);f=0;for(m=k.length;fd;return!0};u.isDataTable=u.fnIsDataTable=function(a){var b=l(a).get(0),c=!1;if(a instanceof u.Api)return!0;l.each(u.settings,function(d,e){d=e.nScrollHead?l("table",e.nScrollHead)[0]:null;var h=e.nScrollFoot?l("table",e.nScrollFoot)[0]:null;if(e.nTable===b||d===b||h===b)c=!0});return c};u.tables=u.fnTables=function(a){var b= +!1;l.isPlainObject(a)&&(b=a.api,a=a.visible);var c=l.map(u.settings,function(d){if(!a||a&&l(d.nTable).is(":visible"))return d.nTable});return b?new B(c):c};u.camelToHungarian=P;y("$()",function(a,b){b=this.rows(b).nodes();b=l(b);return l([].concat(b.filter(a).toArray(),b.find(a).toArray()))});l.each(["on","one","off"],function(a,b){y(b+"()",function(){var c=Array.prototype.slice.call(arguments);c[0]=l.map(c[0].split(/\s/),function(e){return e.match(/\.dt\b/)?e:e+".dt"}).join(" ");var d=l(this.tables().nodes()); +d[b].apply(d,c);return this})});y("clear()",function(){return this.iterator("table",function(a){Ka(a)})});y("settings()",function(){return new B(this.context,this.context)});y("init()",function(){var a=this.context;return a.length?a[0].oInit:null});y("data()",function(){return this.iterator("table",function(a){return U(a.aoData,"_aData")}).flatten()});y("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,h=b.nTBody,f=b.nTHead, +g=b.nTFoot,k=l(e);h=l(h);var m=l(b.nTableWrapper),n=l.map(b.aoData,function(t){return t.nTr}),p;b.bDestroying=!0;F(b,"aoDestroyCallback","destroy",[b]);a||(new B(b)).columns().visible(!0);m.off(".DT").find(":not(tbody *)").off(".DT");l(z).off(".DT-"+b.sInstance);e!=f.parentNode&&(k.children("thead").detach(),k.append(f));g&&e!=g.parentNode&&(k.children("tfoot").detach(),k.append(g));b.aaSorting=[];b.aaSortingFixed=[];Sa(b);l(n).removeClass(b.asStripeClasses.join(" "));l("th, td",f).removeClass(d.sSortable+ +" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);h.children().detach();h.append(n);f=a?"remove":"detach";k[f]();m[f]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),k.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&h.children().each(function(t){l(this).addClass(b.asDestroyStripes[t%p])}));c=l.inArray(b,u.settings);-1!==c&&u.settings.splice(c,1)})});l.each(["column","row","cell"],function(a,b){y(b+"s().every()",function(c){var d=this.selector.opts,e= +this;return this.iterator(b,function(h,f,g,k,m){c.call(e[b](f,"cell"===b?g:d,"cell"===b?d:q),f,g,k,m)})})});y("i18n()",function(a,b,c){var d=this.context[0];a=na(a)(d.oLanguage);a===q&&(a=b);c!==q&&l.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)});u.version="1.11.4";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,"return":!1};u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"", +src:null,idx:-1};u.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};u.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10, +25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null, +fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}}, +fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)", +sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:l.extend({},u.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};E(u.defaults);u.defaults.column={aDataSort:null, +iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};E(u.defaults.column);u.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null, +iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[], +aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:q,oAjaxData:q,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0, +bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==Q(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==Q(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,h= +e.bPaginate;return e.bServerSide?!1===h||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!h||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=M={buttons:{},classes:{},build:"dt/dt-1.11.4/cr-1.5.5/fh-3.2.1/r-2.2.9",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:u.fnVersionCheck, +iApiIndex:0,oJUIClasses:{},sVersion:u.version};l.extend(M,{afnFiltering:M.search,aTypes:M.type.detect,ofnSearch:M.type.search,oSort:M.type.order,afnSortData:M.order,aoFeatures:M.feature,oApi:M.internal,oStdClasses:M.classes,oPagination:M.pager});l.extend(u.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter", +sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody", +sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ec=u.ext.pager;l.extend(ec,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Da(a,b)]},simple_numbers:function(a,b){return["previous",Da(a,b),"next"]}, +full_numbers:function(a,b){return["first","previous",Da(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Da(a,b),"last"]},_numbers:Da,numbers_length:7});l.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,h){var f=a.oClasses,g=a.oLanguage.oPaginate,k=a.oLanguage.oAria.paginate||{},m,n,p=0,t=function(x,w){var r,C=f.sPageButtonDisabled,G=function(I){Ra(a,I.data.action,!0)};var aa=0;for(r=w.length;aa").appendTo(x); +t(O,L)}else{m=null;n=L;O=a.iTabIndex;switch(L){case "ellipsis":x.append('');break;case "first":m=g.sFirst;0===e&&(O=-1,n+=" "+C);break;case "previous":m=g.sPrevious;0===e&&(O=-1,n+=" "+C);break;case "next":m=g.sNext;if(0===h||e===h-1)O=-1,n+=" "+C;break;case "last":m=g.sLast;if(0===h||e===h-1)O=-1,n+=" "+C;break;default:m=a.fnFormatNumber(L+1),n=e===L?f.sPageButtonActive:""}null!==m&&(O=l("",{"class":f.sPageButton+" "+n,"aria-controls":a.sTableId,"aria-label":k[L], +"data-dt-idx":p,tabindex:O,id:0===c&&"string"===typeof L?a.sTableId+"_"+L:null}).html(m).appendTo(x),ob(O,{action:L},G),p++)}}};try{var v=l(b).find(A.activeElement).data("dt-idx")}catch(x){}t(l(b).empty(),d);v!==q&&l(b).find("[data-dt-idx="+v+"]").trigger("focus")}}});l.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return tb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!uc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||Z(a)?"date":null},function(a, +b){b=b.oLanguage.sDecimal;return tb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return Z(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);l.extend(u.ext.type.search,{html:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," ").replace(Va,""):""},string:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," "):a}});var Ua=function(a, +b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=ic(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};l.extend(M.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return Z(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return Z(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return a< +b?1:a>b?-1:0}});Xa("");l.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c,d){l("
      ").addClass(d.sSortJUIWrapper).append(b.contents()).append(l("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+ +" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==g[e]?d.sSortJUIAsc:"desc"==g[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var yb=function(a){Array.isArray(a)&&(a=a.join(","));return"string"===typeof a?a.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):a};u.render= +{number:function(a,b,c,d,e){return{display:function(h){if("number"!==typeof h&&"string"!==typeof h)return h;var f=0>h?"-":"",g=parseFloat(h);if(isNaN(g))return yb(h);g=g.toFixed(c);h=Math.abs(g);g=parseInt(h,10);h=c?b+(h-g).toFixed(c).substring(2):"";0===g&&0===parseFloat(h)&&(f="");return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+h+(e||"")}}},text:function(){return{display:yb,filter:yb}}};l.extend(u.ext.internal,{_fnExternApiFunc:fc,_fnBuildAjax:Oa,_fnAjaxUpdate:Gb,_fnAjaxParameters:Pb, +_fnAjaxUpdateDraw:Qb,_fnAjaxDataSrc:Aa,_fnAddColumn:Ya,_fnColumnOptions:Ga,_fnAdjustColumnSizing:ta,_fnVisibleToColumnIndex:ua,_fnColumnIndexToVisible:va,_fnVisbleColumns:oa,_fnGetColumns:Ia,_fnColumnTypes:$a,_fnApplyColumnDefs:Db,_fnHungarianMap:E,_fnCamelToHungarian:P,_fnLanguageCompat:ma,_fnBrowserDetect:Bb,_fnAddData:ia,_fnAddTr:Ja,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==q?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return l.inArray(c,a.aoData[b].anCells)},_fnGetCellData:T, +_fnSetCellData:Eb,_fnSplitObjNotation:cb,_fnGetObjectDataFn:na,_fnSetObjectDataFn:ha,_fnGetDataMaster:db,_fnClearTable:Ka,_fnDeleteIndex:La,_fnInvalidate:wa,_fnGetRowElements:bb,_fnCreateTr:ab,_fnBuildHead:Fb,_fnDrawHead:ya,_fnDraw:ja,_fnReDraw:ka,_fnAddOptionsHtml:Ib,_fnDetectHeader:xa,_fnGetUniqueThs:Na,_fnFeatureHtmlFilter:Kb,_fnFilterComplete:za,_fnFilterCustom:Tb,_fnFilterColumn:Sb,_fnFilter:Rb,_fnFilterCreateSearch:ib,_fnEscapeRegex:jb,_fnFilterData:Ub,_fnFeatureHtmlInfo:Nb,_fnUpdateInfo:Xb, +_fnInfoMacros:Yb,_fnInitialise:Ba,_fnInitComplete:Pa,_fnLengthChange:kb,_fnFeatureHtmlLength:Jb,_fnFeatureHtmlPaginate:Ob,_fnPageChange:Ra,_fnFeatureHtmlProcessing:Lb,_fnProcessingDisplay:V,_fnFeatureHtmlTable:Mb,_fnScrollDraw:Ha,_fnApplyToChildren:ca,_fnCalculateColumnWidths:Za,_fnThrottle:hb,_fnConvertToWidth:Zb,_fnGetWidestNode:$b,_fnGetMaxLenString:ac,_fnStringToCss:K,_fnSortFlatten:pa,_fnSort:Hb,_fnSortAria:cc,_fnSortListener:nb,_fnSortAttachListener:fb,_fnSortingClasses:Sa,_fnSortData:bc,_fnSaveState:qa, +_fnLoadState:dc,_fnImplementState:pb,_fnSettingsFromNode:Ta,_fnLog:da,_fnMap:X,_fnBindAction:ob,_fnCallbackReg:R,_fnCallbackFire:F,_fnLengthOverflow:lb,_fnRenderer:gb,_fnDataSource:Q,_fnRowAttributes:eb,_fnExtend:qb,_fnCalculateEnd:function(){}});l.fn.dataTable=u;u.$=l;l.fn.dataTableSettings=u.settings;l.fn.dataTableExt=u.ext;l.fn.DataTable=function(a){return l(this).dataTable(a).api()};l.each(u,function(a,b){l.fn.DataTable[a]=b});return u}); /*! diff --git a/modules/http/webif/gstatic/datatables/img/details_close.png b/modules/http/webif/gstatic/datatables/img/details_close.png deleted file mode 100644 index 9c7d6982d9a1408f24278ed38458bcf4722f365f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 686 zcmV;f0#W^mP)rc9KHq9BMrrU)X4H-ZEQYRE1Mf85oDS6+Hm_uUD?Zq_fw zn=!^pDk~VX)hyi>=S**$PUrUR`S!lB@UYy4U- zlEcGD4Gkgv=rJ;WKMI))DAuw^dT%HXWm=_w?8Z*(BJ3CQI%#--o7^rMEd#LBM)@B$J!$?OvK`BMr zRsP$^2&f}b(NMqg?(VM1WPEB0UtKPttw@_o;Zl6jYuxh?_E**)MMmZ=lj-GUY_zo4 zX(g@tS*_O65-5I9{ZVRcW0RMmjZRMDLvyp>RI5|PAGtc61d;J^<~u33U#`0zaEJU_|e`8Yr*V+r?U8t8q~5ysN3H=`#OLdQ`{tkJnH+Sb-{!Kx_E1b-CDbv<6zhT-G!; zHrm-k^-4++%<@a!5cN&=qHLfPW&NcnA1I0WN?Z#PQ?_Tq4T=XPJ+fmXr9Rgh3WX*Z zY#nYzQEw57`)ss=4Tg%vFE(OA%kWiDnx_5WNX2@ofwe7&7_56;heB6A3S0$9q(X_2 z`oVfov$L}iHK*xu4Qu;rVd=8M;tWJwyMNQvvP;;W?hU$~2rlvFo8-^N?*Lg_D zTe3=V`<3HIr6d4&egY9GOIM|W1%+nkgGI!)I?U}_s`qE~MPPaOgl(?}8>tURI zbq@QVWUtD}o>Q37XTTcP8e{XvVEg@k7er&fzT$lU1!T4DL)K$KEZM8pr+dy|-ehDF_X77(*?9ze9%f;;Q2ML_ci!DaFdSTY^)igBw_c9}Yfb<4e2tHf re}@wNRi3XkJ+L;i)(3W7{ssI7YL=IQ_s#k*00000NkvXXu0mjfLt|7w From 34d1b44aa6b45352ee42595c03bb81b073fe3e45 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 6 Feb 2022 01:40:19 +0100 Subject: [PATCH 71/95] datatables: some more improvements and fixes in defaults file. --- .../http/webif/gstatic/datatables/datatables.defaults.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/http/webif/gstatic/datatables/datatables.defaults.js b/modules/http/webif/gstatic/datatables/datatables.defaults.js index c5232fdc04..b53cd3d60b 100644 --- a/modules/http/webif/gstatic/datatables/datatables.defaults.js +++ b/modules/http/webif/gstatic/datatables/datatables.defaults.js @@ -30,19 +30,23 @@ $(window).bind('datatables_defaults', function() { colReorder: true, // enable colomn reorder by drag and drop fixedHeader: {header: true, // header will always be visible on top of page when scrolling headerOffset: $('#webif-navbar').outerHeight() + $('#webif-tabs').outerHeight()}, - responsive: true, // enable responsive extension. autoWidth: false, + initComplete: function () {$('#maintable').show();}, // might want to show table only after init + responsive: {details: {renderer: $.fn.dataTable.Responsive.renderer.listHiddenNodes()}}, //makes it possible to update columns even if they are not shown as columns (but as collapsable items) "fnDrawCallback": function(oSettings) { // hide pagination if not needed if (oSettings._iDisplayLength > oSettings.fnRecordsDisplay() || oSettings._iDisplayLength == -1) { $(oSettings.nTableWrapper).find('.dataTables_paginate').hide(); } else { $(oSettings.nTableWrapper).find('.dataTables_paginate').show(); } + $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.enable( false ); + $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.enable( true ); $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.adjust(); } }); // Set date format for correct sorting of columns containing date strings $.fn.dataTable.moment('DD.MM.YYYY HH:mm:ss'); + $.fn.dataTable.moment('YYYY-MM-DD HH:mm:ss'); $('a[data-toggle="tab"]').on('shown.bs.tab', function(e){ $.fn.dataTable.tables({ visible: true, api: true }).columns.adjust(); $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.adjust(); From 11589fe81b5e2ec536f479c81025b2c9d8eb160f Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 7 Feb 2022 02:33:41 +0100 Subject: [PATCH 72/95] http module webif: add pluginstyles block --- modules/http/webif/gtemplates/base.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/http/webif/gtemplates/base.html b/modules/http/webif/gtemplates/base.html index 8c8a1ce1ba..ba7a206450 100755 --- a/modules/http/webif/gtemplates/base.html +++ b/modules/http/webif/gtemplates/base.html @@ -38,6 +38,9 @@ + + {% block pluginstyles %} + {% endblock pluginstyles %} {%- endblock styles %} {% block scripts %} From 3ee5b4a6f80ca8dd4347f4ad4675eb762e70ad66 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 7 Feb 2022 02:34:00 +0100 Subject: [PATCH 73/95] http module webif: adjust shngInsertText function for updating table cells correctly --- modules/http/webif/gstatic/js/smarthomeng.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/http/webif/gstatic/js/smarthomeng.js b/modules/http/webif/gstatic/js/smarthomeng.js index 42615515d1..ea5002935b 100644 --- a/modules/http/webif/gstatic/js/smarthomeng.js +++ b/modules/http/webif/gstatic/js/smarthomeng.js @@ -50,8 +50,21 @@ function shngPost(path, params, method='post') { * @param {string} text to insert */ -function shngInsertText (id, text) { - document.getElementById(id).innerHTML = text; +function shngInsertText (id, text, table_id=null) { + if (table_id == null) { + document.getElementById(id).innerHTML = text; + } + else { + try { + if ( $.fn.dataTable.isDataTable(table_id) ) { + table = $('#' + table_id).DataTable(); + } + table.cell( $('#' + $.escapeSelector(id)) ).data(text) ; + } + catch (e) { + console.log("Problem setting cell with id " + id + " of table " + table_id + ". Error: " + e); + } + } } From 404289e9b47f7094938d91f782e45708565268a3 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 7 Feb 2022 02:35:20 +0100 Subject: [PATCH 74/95] webinterface docu: implement and improve info on datatables --- .../plugins/webinterface_automatic_update.rst | 73 ++++++++++++++++--- .../webinterface_filling_webinterface.rst | 24 +++--- 2 files changed, 74 insertions(+), 23 deletions(-) diff --git a/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst b/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst index 8c42472d71..453b252371 100644 --- a/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst +++ b/doc/user/source/entwicklung/plugins/webinterface_automatic_update.rst @@ -67,7 +67,7 @@ Die Klasse ``WebInterface`` im Plugin Code muss so erweitert werden, dass sie di Die optionale Möglichkeit einen ``dataSet`` anzugeben, ist für zukünftige Erweiterungen vorgesehen. -Darüber soll es möglich werden, Daten in unterschiedlichen Zyklen zu aktualisieren +Darüber soll es möglich werden, Daten in unterschiedlichen Zyklen zu aktualisieren (z.B. für Daten, deren Ermittlung eine längere Zeit in Anspruch nimmt). @@ -100,7 +100,7 @@ Bei Tabellen werden die einzelnen Datenzeilen beim Rendern durch die for-Schleif {% block **bodytab1** %}
      -
      {% block buttons %} From 16f81a8eb4db1a5cdf2cd1720fc011ddadd6e582 Mon Sep 17 00:00:00 2001 From: msinn Date: Thu, 3 Feb 2022 13:45:56 +0100 Subject: [PATCH 66/95] lib.scheduler: Bugfix for 'str' object has no attribute 'get' (line 643) --- lib/scheduler.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/scheduler.py b/lib/scheduler.py index 83ddcce5f1..2f7d2fccfd 100644 --- a/lib/scheduler.py +++ b/lib/scheduler.py @@ -640,9 +640,12 @@ def _task(self, name, obj, by, source, dest, value): elif obj.__class__.__name__ == 'Item': try: - scheduler_source = source.get('source', '') - if scheduler_source != '': - scheduler_source = ':'+scheduler_source+':'+source.get('details','') + if isinstance(source, str): + scheduler_source = source + else: + scheduler_source = source.get('source', '') + if scheduler_source != '': + scheduler_source = ':'+scheduler_source+':'+source.get('details','') if value is not None: obj(value, caller=("Scheduler"+scheduler_source)) except Exception as e: From 892d352428b2f8a2d6c30f5715418b978e391c18 Mon Sep 17 00:00:00 2001 From: msinn Date: Thu, 3 Feb 2022 13:47:12 +0100 Subject: [PATCH 67/95] lib.item: Fix for non numeric values on attribute crontab: init = xyz --- lib/item/item.py | 9 ++++++--- lib/network.py | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/item/item.py b/lib/item/item.py index 07f74c2d29..991819b362 100644 --- a/lib/item/item.py +++ b/lib/item/item.py @@ -1285,13 +1285,16 @@ def __run_eval(self, value=None, caller='Eval', source=None, dest=None): logger.debug("Item {}: Eval triggered by: {}. Evaluating item with value {}. Eval expression: {}".format(self._path, self.__triggered_by, value, self._eval)) #value = eval(self._eval) - # if contab: init = x is set, x is transfered as a string, for that case re-try eval with x converted to float + + # ms if contab: init = x is set, x is transfered as a string, for that case re-try eval with x converted to float try: - value = eval(self._eval) + value = eval(self._eval) except: - value = float(value) + #value = float(value) + value = self._value = self.cast(value) value = eval(self._eval) # ms end + except Exception as e: # adding "None" as the "destination" information at end of triggered_by # This helps figuring out whether an eval expression was successfully evaluated or not. diff --git a/lib/network.py b/lib/network.py index e5bd398572..9cb3576885 100644 --- a/lib/network.py +++ b/lib/network.py @@ -696,7 +696,7 @@ def _connect_thread_worker(self): while not self._is_connected and self._connect_counter < self._connect_retries and self.__running: self._connect() if self._is_connected: - try: + try: self.__connect_threadlock.release() if self._connected_callback: self._connected_callback(self) @@ -768,7 +768,7 @@ def __receive_thread_worker(self): timeout = True # Check if incoming message is not empty if msg: - # TODO: doing this breaks line separation if multiple lines + # TODO: doing this breaks line separation if multiple lines # are read at a time, the next loop can't split it # because line endings are missing # find out reason for this operation... @@ -842,7 +842,7 @@ def __receive_thread_worker(self): else: self._log_exception(ex, f'lib.network receive thread died with unexpected error: {ex}. Go tell...') self._is_receiving = False - + def _log_exception(self, ex, msg): self.logger.error(msg + ' -- If stack trace is necessary, enable/check debug log') From 78a8ca0106ed6e6396d769546f0fff6f18678def Mon Sep 17 00:00:00 2001 From: Bernd Meiners Date: Fri, 4 Feb 2022 15:22:19 +0100 Subject: [PATCH 68/95] core: fix a bug in relation to sunset with offset --- lib/triggertimes.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/triggertimes.py b/lib/triggertimes.py index 1c3b337807..0a05bcaf40 100644 --- a/lib/triggertimes.py +++ b/lib/triggertimes.py @@ -4,7 +4,7 @@ # Copyright 2016-2020 Martin Sinn m.sinn@gmx.de # Copyright 2016 Christian Straßburg c.strassburg@gmx.de # Copyright 2012-2013 Marcus Popp marcus@popp.mx -# Copyright 2019-2021 Bernd Meiners Bernd.Meiners@mail.de +# Copyright 2019-2022 Bernd Meiners Bernd.Meiners@mail.de ######################################################################### # This file is part of SmartHomeNG. # @@ -33,9 +33,8 @@ from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU from dateutil.tz import tzutc -#print("lib.triggertimes is being imported") from lib.shtime import Shtime -shtime = None +shtime = Shtime.get_instance() logger = logging.getLogger(__name__) @@ -773,7 +772,8 @@ def split_offset(skyevent): @staticmethod def keep_in_range(value, minvalue, maxvalue): - assert minvalue <= maxvalue + if minvalue > maxvalue: + logger.error(f"minvalue={minvalue} is greater than maxvalue={maxvalue}") if value < minvalue: value = minvalue logger.warning(f"{value}<{minvalue} --> {value}={minvalue}") @@ -921,14 +921,8 @@ def get_next(self, starttime: datetime): # time in next_time will be in utctime. So we need to adjust it if eventtime.tzinfo == tzutc(): eventtime = eventtime.astimezone(Skytime.sh.shtime.tzinfo()) - if starttime > eventtime: - logger.error(f"eventtime {eventtime} is earlier than starttime {starttime} for {self.event}(doff={self.doff},moff={self.moff}, searchtime={searchtime}); maybe ephem>4.1 with errors") - #starttime = starttime.replace(hour=0,minute=0,second=0,microsecond=0)+datetime.timedelta(days=1) - continue - else: - logger.debug(f"starting with {starttime} the next {self.event}({self.doff},{self.moff}) is {eventtime}") else: - logger.warning("searchtime.tzinfo was not given as utc!") + logger.error("searchtime.tzinfo was not given as utc!") else: logger.error(f'No function found to get next skyevent time for {self._triggertime}') return get_invalid_time() @@ -938,18 +932,25 @@ def get_next(self, starttime: datetime): # - searchtime must be smaller than eventtime and # - eventtime might be one or more day(s) later + logger.debug(f"starting with {starttime} the next {self.event}({self.doff},{self.moff}) is {eventtime}") + # if the dates differ then it must be certain that the new date adheres to the # constraints of the day range. if eventtime.date() > searchtime.date(): logger.debug(f"eventtime ({eventtime.date()}) is at least a day later than current searchtime ({searchtime}), skip to eventtime's early morning") - searchtime = eventtime.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=Skytime.sh.shtime.tzinfo()) + searchtime = eventtime.replace(hour=0, minute=0, second=0, microsecond=0) continue # need to start over for a matching date - # eventtime and searchtime have the same date + if eventtime.date() < searchtime.date(): + logger.debug(f"eventtime ({eventtime.date()}) is at least a day earlier than current searchtime ({searchtime}), skip to searchtime's early morning") + searchtime = searchtime.replace(hour=0, minute=0, second=0, microsecond=0) + datetime.timedelta(days=1) + continue + + # eventtime and searchtime have the same day # now check time limits if given if self.h_min is not None and self.m_min is not None: try: - dmin = eventtime.replace(hour=self.h_min, minute=self.m_min, second=0, microsecond=0, tzinfo=Skytime.sh.shtime.tzinfo()) + dmin = eventtime.replace(hour=self.h_min, minute=self.m_min, second=0, microsecond=0) except Exception: logger.error('Wrong syntax: {self._triggertime}. Should be [H:M<](skyevent)[+|-][offset][ Date: Fri, 4 Feb 2022 16:53:15 +0100 Subject: [PATCH 69/95] datatables: improve defaults and fix some issues --- .../http/webif/gstatic/datatables/datatables.defaults.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/http/webif/gstatic/datatables/datatables.defaults.js b/modules/http/webif/gstatic/datatables/datatables.defaults.js index c1a24488b4..c5232fdc04 100644 --- a/modules/http/webif/gstatic/datatables/datatables.defaults.js +++ b/modules/http/webif/gstatic/datatables/datatables.defaults.js @@ -24,8 +24,8 @@ $(window).bind('datatables_defaults', function() { { // Set datatable useful defaults $.extend( $.fn.dataTable.defaults, { - lengthMenu: [ [10, 25, 50, -1], [10, 25, 50, "All"] ], // pagination menu - pageLength: -1, // default to "all" + lengthMenu: [ [25, 50, 100, -1], [25, 50, 100, "All"] ], // pagination menu + pageLength: 100, // default to "all" pagingType: "full_numbers", // include first and last in pagination menu colReorder: true, // enable colomn reorder by drag and drop fixedHeader: {header: true, // header will always be visible on top of page when scrolling @@ -38,12 +38,14 @@ $(window).bind('datatables_defaults', function() { } else { $(oSettings.nTableWrapper).find('.dataTables_paginate').show(); } + $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.adjust(); } }); // Set date format for correct sorting of columns containing date strings $.fn.dataTable.moment('DD.MM.YYYY HH:mm:ss'); $('a[data-toggle="tab"]').on('shown.bs.tab', function(e){ - $.fn.dataTable.tables({ visible: true, api: true }).columns.adjust().draw(); + $.fn.dataTable.tables({ visible: true, api: true }).columns.adjust(); + $.fn.dataTable.tables({ visible: true, api: true }).fixedHeader.adjust(); $.fn.dataTable.tables({ visible: true, api: true }).responsive.recalc(); }); } From 04a91cb50f57048b4d699006f1d9803b4776dbf1 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Fri, 4 Feb 2022 16:57:19 +0100 Subject: [PATCH 70/95] datatables: update to 1.11.4 --- .../gstatic/datatables/datatables.min.css | 6 +- .../gstatic/datatables/datatables.min.js | 292 +++++++++--------- .../gstatic/datatables/img/details_close.png | Bin 686 -> 0 bytes .../gstatic/datatables/img/details_open.png | Bin 709 -> 0 bytes 4 files changed, 149 insertions(+), 149 deletions(-) delete mode 100644 modules/http/webif/gstatic/datatables/img/details_close.png delete mode 100644 modules/http/webif/gstatic/datatables/img/details_open.png diff --git a/modules/http/webif/gstatic/datatables/datatables.min.css b/modules/http/webif/gstatic/datatables/datatables.min.css index 71b692d0de..d86236f76e 100644 --- a/modules/http/webif/gstatic/datatables/datatables.min.css +++ b/modules/http/webif/gstatic/datatables/datatables.min.css @@ -4,13 +4,13 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.11.3/cr-1.5.5/fh-3.2.1/r-2.2.9 + * https://datatables.net/download/#dt/dt-1.11.4/cr-1.5.5/fh-3.2.1/r-2.2.9 * * Included libraries: - * DataTables 1.11.3, ColReorder 1.5.5, FixedHeader 3.2.1, Responsive 2.2.9 + * DataTables 1.11.4, ColReorder 1.5.5, FixedHeader 3.2.1, Responsive 2.2.9 */ -td.dt-control{background:url("img/details_open.png") no-repeat center center;cursor:pointer}tr.dt-hasChild td.dt-control{background:url("img/details_close.png") no-repeat center center}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("img/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("img/sort_asc.png") !important}table.dataTable thead .sorting_desc{background-image:url("img/sort_desc.png") !important}table.dataTable thead .sorting_asc_disabled{background-image:url("img/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("img/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#b0bed9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, white 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, white 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, white 0%, #dcdcdc 100%);background:-o-linear-gradient(top, white 0%, #dcdcdc 100%);background:linear-gradient(to bottom, white 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));background:-webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} +table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{height:1em;width:1em;margin-top:-9px;display:inline-block;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable tr.dt-hasChild td.dt-control:before{content:"-";background-color:#d33333}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("img/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("img/sort_asc.png") !important}table.dataTable thead .sorting_desc{background-image:url("img/sort_desc.png") !important}table.dataTable thead .sorting_asc_disabled{background-image:url("img/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("img/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#b0bed9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, white 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, white 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, white 0%, #dcdcdc 100%);background:-o-linear-gradient(top, white 0%, #dcdcdc 100%);background:linear-gradient(to bottom, white 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));background:-webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} table.DTCR_clonedTable.dataTable{position:absolute !important;background-color:rgba(255, 255, 255, 0.7);z-index:202}div.DTCR_pointer{width:1px;background-color:#0259c4;z-index:201} diff --git a/modules/http/webif/gstatic/datatables/datatables.min.js b/modules/http/webif/gstatic/datatables/datatables.min.js index f50e7f57b2..6d7ff7955f 100644 --- a/modules/http/webif/gstatic/datatables/datatables.min.js +++ b/modules/http/webif/gstatic/datatables/datatables.min.js @@ -4,10 +4,10 @@ * * To rebuild or modify this file with the latest versions of the included * software please visit: - * https://datatables.net/download/#dt/dt-1.11.3/cr-1.5.5/fh-3.2.1/r-2.2.9 + * https://datatables.net/download/#dt/dt-1.11.4/cr-1.5.5/fh-3.2.1/r-2.2.9 * * Included libraries: - * DataTables 1.11.3, ColReorder 1.5.5, FixedHeader 3.2.1, Responsive 2.2.9 + * DataTables 1.11.4, ColReorder 1.5.5, FixedHeader 3.2.1, Responsive 2.2.9 */ /*! @@ -21,7 +21,7 @@ or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. For details please refer to: http://www.datatables.net - DataTables 1.11.3 + DataTables 1.11.4 ©2008-2021 SpryMedia Ltd - datatables.net/license */ var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(l,z,A){l instanceof String&&(l=String(l));for(var q=l.length,E=0;E").css({position:"fixed",top:0,left:-1*l(z).scrollLeft(),height:1, width:1,overflow:"hidden"}).append(l("
      ").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(l("
      ").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}l.extend(a.oBrowser,u.__browser);a.oScroll.iBarWidth=u.__browser.barWidth} -function Cb(a,b,c,d,e,h){var f=!1;if(c!==q){var g=c;f=!0}for(;d!==e;)a.hasOwnProperty(d)&&(g=f?b(g,a[d],d,a):a[d],f=!0,d+=h);return g}function Xa(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=l.extend({},u.models.oColumn,c,{nTh:b?b:A.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=l.extend({},u.models.oSearch,c[d]);Ga(a,d,l(b).data())}function Ga(a,b,c){b=a.aoColumns[b]; +function Cb(a,b,c,d,e,h){var f=!1;if(c!==q){var g=c;f=!0}for(;d!==e;)a.hasOwnProperty(d)&&(g=f?b(g,a[d],d,a):a[d],f=!0,d+=h);return g}function Ya(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=l.extend({},u.models.oColumn,c,{nTh:b?b:A.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=l.extend({},u.models.oSearch,c[d]);Ga(a,d,l(b).data())}function Ga(a,b,c){b=a.aoColumns[b]; var d=a.oClasses,e=l(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var h=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);h&&(b.sWidthOrig=h[1])}c!==q&&null!==c&&(Ab(c),P(u.defaults.column,c,!0),c.mDataProp===q||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),l.extend(b,c),X(b,c,"sWidth","sWidthOrig"),c.iDataSort!==q&&(b.aDataSort=[c.iDataSort]),X(b,c,"aDataSort"));var f=b.mData,g=na(f), k=b.mRender?na(b.mRender):null;c=function(m){return"string"===typeof m&&-1!==m.indexOf("@")};b._bAttrSrc=l.isPlainObject(f)&&(c(f.sort)||c(f.type)||c(f.filter));b._setter=null;b.fnGetData=function(m,n,p){var t=g(m,n,q,p);return k&&n?k(t,n,m,p):t};b.fnSetData=function(m,n,p){return ha(f)(m,n,p)};"number"!==typeof f&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==l.inArray("asc",b.asSorting);c=-1!==l.inArray("desc",b.asSorting);b.bSortable&&(a||c)?a&&!c? -(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function ta(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ya(a);for(var c=0,d=b.length;cm[n])d(g.length+m[n],k);else if("string"===typeof m[n]){var p=0;for(f=g.length;pb&&a[e]--; -1!=d&&c===q&&a.splice(d,1)}function wa(a,b,c,d){var e=a.aoData[b],h,f=function(k,m){for(;k.childNodes.length;)k.removeChild(k.firstChild);k.innerHTML=T(a,b,m,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var g=e.anCells;if(g)if(d!==q)f(g[d],d);else for(c=0,h=g.length;c").appendTo(d));var k=0;for(b=g.length;k=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);g=a._iDisplayStart;var n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,V(a,!1);else if(!k)a.iDraw++;else if(!a.bDestroying&&!b){Gb(a);return}if(0!==m.length)for(b=k?a.aoData.length:n,f=k?0:g;f",{"class":h?e[0]:""}).append(l("
      ",{valign:"top",colSpan:oa(a),"class":a.oClasses.sRowEmpty}).html(d))[0];F(a,"aoHeaderCallback","header",[l(a.nTHead).children("tr")[0],cb(a),g,n,m]);F(a,"aoFooterCallback", -"footer",[l(a.nTFoot).children("tr")[0],cb(a),g,n,m]);e=l(a.nTBody);e.children().detach();e.append(l(c));F(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ka(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Hb(a);d?za(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;ja(a);a._drawHold=!1}function Ib(a){var b=a.oClasses,c=l(a.nTable);c=l("
      ").insertBefore(c);var d=a.oFeatures,e=l("
      ",{id:a.sTableId+"_wrapper", +(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function ta(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Za(a);for(var c=0,d=b.length;cm[n])d(g.length+m[n],k);else if("string"===typeof m[n]){var p=0;for(f=g.length;pb&&a[e]--; -1!=d&&c===q&&a.splice(d,1)}function wa(a,b,c,d){var e=a.aoData[b],h,f=function(k,m){for(;k.childNodes.length;)k.removeChild(k.firstChild);k.innerHTML=T(a,b,m,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var g=e.anCells;if(g)if(d!==q)f(g[d],d);else for(c=0,h=g.length;c").appendTo(d));var k=0;for(b=g.length;k=a.fnRecordsDisplay()?0:d,a.iInitDisplayStart=-1);c=F(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==l.inArray(!1,c))V(a,!1);else{c=[];var e=0;d=a.asStripeClasses;var h=d.length,f=a.oLanguage,g="ssp"==Q(a),k=a.aiDisplay,m=a._iDisplayStart,n=a.fnDisplayEnd();a.bDrawing=!0;if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,V(a,!1);else if(!g)a.iDraw++;else if(!a.bDestroying&&!b){Gb(a);return}if(0!==k.length)for(b=g?a.aoData.length:n,f=g?0:m;f",{"class":h?d[0]:""}).append(l("
      ",{valign:"top",colSpan:oa(a),"class":a.oClasses.sRowEmpty}).html(e))[0];F(a,"aoHeaderCallback","header",[l(a.nTHead).children("tr")[0],db(a),m,n,k]);F(a,"aoFooterCallback", +"footer",[l(a.nTFoot).children("tr")[0],db(a),m,n,k]);d=l(a.nTBody);d.children().detach();d.append(l(c));F(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ka(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Hb(a);d?za(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;ja(a);a._drawHold=!1}function Ib(a){var b=a.oClasses,c=l(a.nTable);c=l("
      ").insertBefore(c);var d=a.oFeatures,e=l("
      ",{id:a.sTableId+"_wrapper", "class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var h=a.sDom.split(""),f,g,k,m,n,p,t=0;t")[0];m=h[t+1];if("'"==m||'"'==m){n="";for(p=2;h[t+p]!=m;)n+=h[t+p],p++;"H"==n?n=b.sJUIHeader:"F"==n&&(n=b.sJUIFooter);-1!=n.indexOf(".")?(m=n.split("."),k.id=m[0].substr(1,m[0].length-1),k.className=m[1]):"#"==n.charAt(0)?k.id=n.substr(1,n.length-1):k.className=n;t+=p}e.append(k); e=l(k)}else if(">"==g)e=e.parent();else if("l"==g&&d.bPaginate&&d.bLengthChange)f=Jb(a);else if("f"==g&&d.bFilter)f=Kb(a);else if("r"==g&&d.bProcessing)f=Lb(a);else if("t"==g)f=Mb(a);else if("i"==g&&d.bInfo)f=Nb(a);else if("p"==g&&d.bPaginate)f=Ob(a);else if(0!==u.ext.feature.length)for(k=u.ext.feature,p=0,m=k.length;p',g=d.sSearch;g=g.match(/_INPUT_/)?g.replace("_INPUT_",f):g+f;b=l("
      ",{id:h.f?null:c+"_filter","class":b.sFilter}).append(l("
      ").addClass(b.sLength);a.aanFeatures.l||(k[0].id=c+"_length");k.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));l("select",k).val(a._iDisplayLength).on("change.DT",function(m){jb(a,l(this).val());ja(a)});l(a.nTable).on("length.dt.DT",function(m,n,p){a===n&&l("select",k).val(p)});return k[0]}function Ob(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(f){ja(f)};b=l("
      ").addClass(a.oClasses.sPaging+b)[0]; -var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(f){if(d){var g=f._iDisplayStart,k=f._iDisplayLength,m=f.fnRecordsDisplay(),n=-1===k;g=n?0:Math.ceil(g/k);k=n?1:Math.ceil(m/k);m=c(g,k);var p;n=0;for(p=h.p.length;nh&& +c.call(a,f?1:Math.ceil(h/e)))}function Ba(a){var b=a.iInitDisplayStart,c=a.aoColumns;var d=a.oFeatures;var e=a.bDeferLoading;if(a.bInitialised){Ib(a);Fb(a);ya(a,a.aoHeader);ya(a,a.aoFooter);V(a,!0);d.bAutoWidth&&Za(a);var h=0;for(d=c.length;h",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect});for(var f=0,g=h.length;f
      ").addClass(b.sLength);a.aanFeatures.l||(k[0].id=c+"_length");k.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));l("select",k).val(a._iDisplayLength).on("change.DT",function(m){kb(a,l(this).val());ja(a)});l(a.nTable).on("length.dt.DT",function(m,n,p){a===n&&l("select",k).val(p)});return k[0]}function Ob(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(f){ja(f)};b=l("
      ").addClass(a.oClasses.sPaging+b)[0]; +var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(f){if(d){var g=f._iDisplayStart,k=f._iDisplayLength,m=f.fnRecordsDisplay(),n=-1===k;g=n?0:Math.ceil(g/k);k=n?1:Math.ceil(m/k);m=c(g,k);var p;n=0;for(p=h.p.length;nh&& (d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function V(a,b){a.oFeatures.bProcessing&&l(a.aanFeatures.r).css("display",b?"block":"none"); F(a,null,"processing",[a,b])}function Mb(a){var b=l(a.nTable),c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,h=a.oClasses,f=b.children("caption"),g=f.length?f[0]._captionSide:null,k=l(b[0].cloneNode(!1)),m=l(b[0].cloneNode(!1)),n=b.children("tfoot");n.length||(n=null);k=l("
      ",{"class":h.sScrollWrapper}).append(l("
      ",{"class":h.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?d?K(d):null:"100%"}).append(l("
      ",{"class":h.sScrollHeadInner}).css({"box-sizing":"content-box", width:c.sXInner||"100%"}).append(k.removeAttr("id").css("margin-left",0).append("top"===g?f:null).append(b.children("thead"))))).append(l("
      ",{"class":h.sScrollBody}).css({position:"relative",overflow:"auto",width:d?K(d):null}).append(b));n&&k.append(l("
      ",{"class":h.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?K(d):null:"100%"}).append(l("
      ",{"class":h.sScrollFootInner}).append(m.removeAttr("id").css("margin-left",0).append("bottom"===g?f:null).append(b.children("tfoot"))))); @@ -82,121 +82,121 @@ b=k.children();var p=b[0];h=b[1];var t=n?b[2]:null;if(d)l(h).on("scroll.DT",func g.style,t=l(a.nScrollFoot).children("div"),v=t.children("table"),x=l(a.nTHead),w=l(a.nTable),r=w[0],C=r.style,G=a.nTFoot?l(a.nTFoot):null,aa=a.oBrowser,L=aa.bScrollOversize;U(a.aoColumns,"nTh");var O=[],I=[],H=[],ea=[],Y,Ca=function(D){D=D.style;D.paddingTop="0";D.paddingBottom="0";D.borderTopWidth="0";D.borderBottomWidth="0";D.height=0};var fa=g.scrollHeight>g.clientHeight;if(a.scrollBarVis!==fa&&a.scrollBarVis!==q)a.scrollBarVis=fa,ta(a);else{a.scrollBarVis=fa;w.children("thead, tfoot").remove(); if(G){var ba=G.clone().prependTo(w);var la=G.find("tr");ba=ba.find("tr")}var mb=x.clone().prependTo(w);x=x.find("tr");fa=mb.find("tr");mb.find("th, td").removeAttr("tabindex");c||(p.width="100%",h[0].style.width="100%");l.each(Na(a,mb),function(D,W){Y=ua(a,D);W.style.width=a.aoColumns[Y].sWidth});G&&ca(function(D){D.style.width=""},ba);h=w.outerWidth();""===c?(C.width="100%",L&&(w.find("tbody").height()>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(w.outerWidth()-b)),h=w.outerWidth()): ""!==d&&(C.width=K(d),h=w.outerWidth());ca(Ca,fa);ca(function(D){var W=z.getComputedStyle?z.getComputedStyle(D).width:K(l(D).width());H.push(D.innerHTML);O.push(W)},fa);ca(function(D,W){D.style.width=O[W]},x);l(fa).height(0);G&&(ca(Ca,ba),ca(function(D){ea.push(D.innerHTML);I.push(K(l(D).css("width")))},ba),ca(function(D,W){D.style.width=I[W]},la),l(ba).height(0));ca(function(D,W){D.innerHTML='
      '+H[W]+"
      ";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow= -"hidden";D.style.width=O[W]},fa);G&&ca(function(D,W){D.innerHTML='
      '+ea[W]+"
      ";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=I[W]},ba);w.outerWidth()g.offsetHeight||"scroll"==n.css("overflow-y")?h+b:h,L&&(g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(la-b)),""!==c&&""===d||da(a,1,"Possible column misalignment",6)):la="100%";p.width=K(la);f.width=K(la);G&&(a.nScrollFoot.style.width= -K(la));!e&&L&&(p.height=K(r.offsetHeight+b));c=w.outerWidth();m[0].style.width=K(c);k.width=K(c);d=w.height()>g.clientHeight||"scroll"==n.css("overflow-y");e="padding"+(aa.bScrollbarLeft?"Left":"Right");k[e]=d?b+"px":"0px";G&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");w.children("colgroup").insertBefore(w.children("thead"));n.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(g.scrollTop=0)}}function ca(a,b,c){for(var d=0,e=0,h=b.length,f,g;e").appendTo(g.find("tbody"));g.find("thead, tfoot").remove();g.append(l(a.nTHead).clone()).append(l(a.nTFoot).clone());g.find("tfoot th, tfoot td").css("width","");m=Na(a,g.find("thead")[0]);for(v=0;v").css({width:w.sWidthOrig, -margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v=0;v").css(h||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(g).appendTo(p);h&&f?g.width(f):h?(g.css("width","auto"),g.removeAttr("width"),g.width()").css("width",K(a)).appendTo(b||A.body);b=a[0].offsetWidth;a.remove();return b}function $b(a,b){var c=ac(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]: -l("
      ").html(T(a,c,b,"display"))[0]}function ac(a,b){for(var c,d=-1,e=-1,h=0,f=a.aoData.length;hd&&(d=c.length,e=h);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function pa(a){var b=[],c=a.aoColumns;var d=a.aaSortingFixed;var e=l.isPlainObject(d);var h=[];var f=function(n){n.length&&!Array.isArray(n[0])?h.push(n):l.merge(h,n)};Array.isArray(d)&&f(d); -e&&d.pre&&f(d.pre);f(a.aaSorting);e&&d.post&&f(d.post);for(a=0;aG?1:0;if(0!==C)return"asc"===r.dir?C:-C}C=c[n];G=c[p];return CG?1:0}):f.sort(function(n,p){var t,v=g.length,x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;tG?1:0})}a.bSorted=!0}function cc(a){var b=a.aoColumns,c=pa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d/g,"");var k=h.nTh;k.removeAttribute("aria-sort");h.bSortable&&(0e?e+1:3))}e=0;for(h=d.length;ee?e+1:3))}a.aLastSort=d}function bc(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,va(a,b)));for(var h,f=u.ext.type.order[c.sType+"-pre"],g=0,k=a.aoData.length;g=e.length?[0, -m[1]]:m)}));b.search!==q&&l.extend(a.oPreviousSearch,Wb(b.search));if(b.columns){f=0;for(d=b.columns.length;f=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function fb(a,b){a=a.renderer;var c=u.ext.renderer[b];return l.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function Q(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Da(a,b){var c=ec.numbers_length,d=Math.floor(c/2); -b<=c?a=ra(0,b):a<=d?(a=ra(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=ra(b-(c-2),b):(a=ra(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function Wa(a){l.each({num:function(b){return Ta(b,a)},"num-fmt":function(b){return Ta(b,a,rb)},"html-num":function(b){return Ta(b,a,Ua)},"html-num-fmt":function(b){return Ta(b,a,Ua,rb)}},function(b,c){M.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(M.type.search[b+a]=M.type.search.html)})}function fc(a){return function(){var b= -[Sa(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a,b){if(this instanceof u)return l(a).DataTable(b);b=a;this.$=function(f,g){return this.api(!0).$(f,g)};this._=function(f,g){return this.api(!0).rows(f,g).data()};this.api=function(f){return f?new B(Sa(this[M.iApiIndex])):new B(this)};this.fnAddData=function(f,g){var k=this.api(!0);f=Array.isArray(f)&&(Array.isArray(f[0])||l.isPlainObject(f[0]))?k.rows.add(f):k.row.add(f); -(g===q||g)&&k.draw();return f.flatten().toArray()};this.fnAdjustColumnSizing=function(f){var g=this.api(!0).columns.adjust(),k=g.settings()[0],m=k.oScroll;f===q||f?g.draw(!1):(""!==m.sX||""!==m.sY)&&Ha(k)};this.fnClearTable=function(f){var g=this.api(!0).clear();(f===q||f)&&g.draw()};this.fnClose=function(f){this.api(!0).row(f).child.hide()};this.fnDeleteRow=function(f,g,k){var m=this.api(!0);f=m.rows(f);var n=f.settings()[0],p=n.aoData[f[0][0]];f.remove();g&&g.call(this,n,p);(k===q||k)&&m.draw(); -return p};this.fnDestroy=function(f){this.api(!0).destroy(f)};this.fnDraw=function(f){this.api(!0).draw(f)};this.fnFilter=function(f,g,k,m,n,p){n=this.api(!0);null===g||g===q?n.search(f,k,m,p):n.column(g).search(f,k,m,p);n.draw()};this.fnGetData=function(f,g){var k=this.api(!0);if(f!==q){var m=f.nodeName?f.nodeName.toLowerCase():"";return g!==q||"td"==m||"th"==m?k.cell(f,g).data():k.row(f).data()||null}return k.data().toArray()};this.fnGetNodes=function(f){var g=this.api(!0);return f!==q?g.row(f).node(): -g.rows().nodes().flatten().toArray()};this.fnGetPosition=function(f){var g=this.api(!0),k=f.nodeName.toUpperCase();return"TR"==k?g.row(f).index():"TD"==k||"TH"==k?(f=g.cell(f).index(),[f.row,f.columnVisible,f.column]):null};this.fnIsOpen=function(f){return this.api(!0).row(f).child.isShown()};this.fnOpen=function(f,g,k){return this.api(!0).row(f).child(g,k).show().child()[0]};this.fnPageChange=function(f,g){f=this.api(!0).page(f);(g===q||g)&&f.draw(!1)};this.fnSetColumnVis=function(f,g,k){f=this.api(!0).column(f).visible(g); -(k===q||k)&&f.columns.adjust().draw()};this.fnSettings=function(){return Sa(this[M.iApiIndex])};this.fnSort=function(f){this.api(!0).order(f).draw()};this.fnSortListener=function(f,g,k){this.api(!0).order.listener(f,g,k)};this.fnUpdate=function(f,g,k,m,n){var p=this.api(!0);k===q||null===k?p.row(g).data(f):p.cell(g,k).data(f);(n===q||n)&&p.columns.adjust();(m===q||m)&&p.draw();return 0};this.fnVersionCheck=M.fnVersionCheck;var c=this,d=b===q,e=this.length;d&&(b={});this.oApi=this.internal=M.internal; -for(var h in u.ext.internal)h&&(this[h]=fc(h));this.each(function(){var f={},g=1").appendTo(t));r.nTHead=H[0];var ea=t.children("tbody");0===ea.length&&(ea=l("
      ").html(T(a,c,b,"display"))[0]}function ac(a,b){for(var c,d=-1,e=-1,h=0,f=a.aoData.length;hd&&(d=c.length,e=h);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function pa(a){var b=[],c=a.aoColumns;var d=a.aaSortingFixed;var e=l.isPlainObject(d);var h=[];var f=function(n){n.length&& +!Array.isArray(n[0])?h.push(n):l.merge(h,n)};Array.isArray(d)&&f(d);e&&d.pre&&f(d.pre);f(a.aaSorting);e&&d.post&&f(d.post);for(a=0;aG?1:0;if(0!==C)return"asc"===r.dir?C:-C}C=c[n];G=c[p];return CG?1:0}):f.sort(function(n,p){var t,v=g.length,x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;tG?1:0})}a.bSorted=!0}function cc(a){var b=a.aoColumns,c=pa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d/g,"");var k=h.nTh;k.removeAttribute("aria-sort");h.bSortable&&(0e?e+1:3))}e= +0;for(h=d.length;ee?e+1:3))}a.aLastSort=d}function bc(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,va(a,b)));for(var h,f=u.ext.type.order[c.sType+"-pre"],g=0,k=a.aoData.length;g=e.length?[0,m[1]]:m)}));b.search!==q&&l.extend(a.oPreviousSearch,Wb(b.search));if(b.columns){f=0;for(d=b.columns.length;f=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function gb(a,b){a=a.renderer;var c=u.ext.renderer[b];return l.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function Q(a){return a.oFeatures.bServerSide? +"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Da(a,b){var c=ec.numbers_length,d=Math.floor(c/2);b<=c?a=ra(0,b):a<=d?(a=ra(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=ra(b-(c-2),b):(a=ra(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function Xa(a){l.each({num:function(b){return Ua(b,a)},"num-fmt":function(b){return Ua(b,a,rb)},"html-num":function(b){return Ua(b,a,Va)},"html-num-fmt":function(b){return Ua(b,a,Va,rb)}},function(b, +c){M.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(M.type.search[b+a]=M.type.search.html)})}function fc(a){return function(){var b=[Ta(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a,b){if(this instanceof u)return l(a).DataTable(b);b=a;this.$=function(f,g){return this.api(!0).$(f,g)};this._=function(f,g){return this.api(!0).rows(f,g).data()};this.api=function(f){return f?new B(Ta(this[M.iApiIndex])):new B(this)};this.fnAddData= +function(f,g){var k=this.api(!0);f=Array.isArray(f)&&(Array.isArray(f[0])||l.isPlainObject(f[0]))?k.rows.add(f):k.row.add(f);(g===q||g)&&k.draw();return f.flatten().toArray()};this.fnAdjustColumnSizing=function(f){var g=this.api(!0).columns.adjust(),k=g.settings()[0],m=k.oScroll;f===q||f?g.draw(!1):(""!==m.sX||""!==m.sY)&&Ha(k)};this.fnClearTable=function(f){var g=this.api(!0).clear();(f===q||f)&&g.draw()};this.fnClose=function(f){this.api(!0).row(f).child.hide()};this.fnDeleteRow=function(f,g,k){var m= +this.api(!0);f=m.rows(f);var n=f.settings()[0],p=n.aoData[f[0][0]];f.remove();g&&g.call(this,n,p);(k===q||k)&&m.draw();return p};this.fnDestroy=function(f){this.api(!0).destroy(f)};this.fnDraw=function(f){this.api(!0).draw(f)};this.fnFilter=function(f,g,k,m,n,p){n=this.api(!0);null===g||g===q?n.search(f,k,m,p):n.column(g).search(f,k,m,p);n.draw()};this.fnGetData=function(f,g){var k=this.api(!0);if(f!==q){var m=f.nodeName?f.nodeName.toLowerCase():"";return g!==q||"td"==m||"th"==m?k.cell(f,g).data(): +k.row(f).data()||null}return k.data().toArray()};this.fnGetNodes=function(f){var g=this.api(!0);return f!==q?g.row(f).node():g.rows().nodes().flatten().toArray()};this.fnGetPosition=function(f){var g=this.api(!0),k=f.nodeName.toUpperCase();return"TR"==k?g.row(f).index():"TD"==k||"TH"==k?(f=g.cell(f).index(),[f.row,f.columnVisible,f.column]):null};this.fnIsOpen=function(f){return this.api(!0).row(f).child.isShown()};this.fnOpen=function(f,g,k){return this.api(!0).row(f).child(g,k).show().child()[0]}; +this.fnPageChange=function(f,g){f=this.api(!0).page(f);(g===q||g)&&f.draw(!1)};this.fnSetColumnVis=function(f,g,k){f=this.api(!0).column(f).visible(g);(k===q||k)&&f.columns.adjust().draw()};this.fnSettings=function(){return Ta(this[M.iApiIndex])};this.fnSort=function(f){this.api(!0).order(f).draw()};this.fnSortListener=function(f,g,k){this.api(!0).order.listener(f,g,k)};this.fnUpdate=function(f,g,k,m,n){var p=this.api(!0);k===q||null===k?p.row(g).data(f):p.cell(g,k).data(f);(n===q||n)&&p.columns.adjust(); +(m===q||m)&&p.draw();return 0};this.fnVersionCheck=M.fnVersionCheck;var c=this,d=b===q,e=this.length;d&&(b={});this.oApi=this.internal=M.internal;for(var h in u.ext.internal)h&&(this[h]=fc(h));this.each(function(){var f={},g=1").appendTo(t));r.nTHead=H[0];var ea=t.children("tbody");0===ea.length&&(ea=l("
      +
      @@ -149,13 +149,13 @@ dass die ID in Wertetabellen eindeutig sind, wird die for-Schleifenvariable (hie {% block **bodytab1** %}
      -
      {{ _('Item') }}
      +
      - - - - + + + + @@ -183,7 +183,7 @@ Das Webinterface ruft regelmäßig eine Methode des Plugins auf, um aktualisiert Wenn die Daten empfangen wurden, werden sie an die JavaScript-Funktion ``handleUpdatedData()`` der Webseite übergeben. Diese Funktion weist dann die neuen Daten den jeweiligen DOM-Elementen zu. -Die Funktion ``handleUpdatedData()`` ist im Block ``pluginscripts`` des HTML-Templates definiert. +Die Funktion ``handleUpdatedData()`` ist im Block ``pluginscripts`` des HTML-Templates definiert. Das folgende Beispiel weist die neuen Daten dem oben vorgestellten
      {{ _('Item') }}{{ _('Typ') }}{{ _('knx_dpt') }}{{ _('Wert') }}{{ _('Item') }}{{ _('Typ') }}{{ _('knx_dpt') }}{{ _('Wert') }}
      -Element des ``headtable`` zu: .. code-block:: html+jinja @@ -194,7 +194,7 @@ Das folgende Beispiel weist die neuen Daten dem oben vorgestellten -Element if (dataSet === 'devices_info' || dataSet === null) { var objResponse = JSON.parse(response); - shngInsertText('fromip', objResponse['fromip']) + shngInsertText('fromip', objResponse['fromip']); } } @@ -212,7 +212,9 @@ Das nächste Beispiel befüllt dazu analog die -Elemente der Zeilen in der T var objResponse = JSON.parse(response); for (var item in objResponse) { - shngInsertText(item+'_value', objResponse['item'][item]['value']) + shngInsertText(item+'_value', objResponse['item'][item]['value']); + // bei Tabellen mit datatables Funktion sollte die Zeile lauten: + // shngInsertText(item+'_value', objResponse['item'][item]['value'], 'maintable'); } } } @@ -220,6 +222,56 @@ Das nächste Beispiel befüllt dazu analog die -Elemente der Zeilen in der T {% endblock pluginscripts %} +Sortierbare Tabellen +-------------------- + +Wie erwähnt muss für das Aktivieren von sortier- und durchsuchbaren Tabellen der entsprechende Script-Block +wie in :doc:`Das Webinterface mit Inhalt füllen ` +unter Punkt 3 beschrieben eingefügt werden. Dabei ist auch zu beachten, dass der zu sortierenden +Tabelle eine entsprechende ID gegeben wird (im Beispiel oben ``#maintable``) und die Klasse ``display`` +ergänzt wird. + +Damit die neuen Daten auch von datatables.js erkannt und korrekt sortiert werden, ist es wichtig, +dem Aufruf ``shngInsertText`` die Tabellen-ID als dritten Parameter mitzugeben (im Beispiel 'maintable'). + +Standardmäßig werden die Spalten automatisch so skaliert, dass sich den Inhalten anpassen. Möchte man +bestimmten Spalten eine konkrete Breite vorgeben, sollte im Block ``pluginstyles`` entsprechender +Code eingefügt werden. Außerdem sind die ```` Tags natürlich mit den entsprechenden Klassen zu bestücken. +Zusätzlich/alternativ kann die Tabelle auch mit dem css Style ``table-layout: fixed;`` versehen werden, +um die dynamische Anpassung der Spaltenbreite gänzlich zu unterbinden. + +.. code-block:: css+jinja + + {% block pluginstyles %} + + {% endblock pluginstyles %} + + +Sollte der Inhalt einer Spalte erwartungsgemäß sehr breit sein, kann die Spalte stattdessen auch +als ausklappbare Informationszeile konfiguriert werden. Dann empfiehlt es sich auf jeden Fall, +wie oben angegeben, die Tabelle per CSS unsichtbar zu machen. Die datatables.js defaults sorgen dafür, +dass die Tabelle nach der kompletten Inititalisierung angezeigt wird. Dadurch wird ein +mögliches Flackern der Seite beim Aufbau verhindert. Die Deklaration der Tabelle im pluginscripts +Block hat dabei wie folgt auszusehen, wobei bei ``targets`` die interne Nummerierung der Spalten +anzugeben ist (0 wäre die erste Tabellenspalte, 2 die zweite, etc.). + +.. code-block:: html+jinja + + table = $('#maintable').DataTable( { + "columnDefs": [{ "targets": 0, "className": "none"}].concat($.fn.dataTable.defaults.columnDefs) + } ); + + Festlegen des Aktualisierungsintervalls --------------------------------------- @@ -234,4 +286,3 @@ Diese wird auf den gewünschten Wert in Millisekunden gesetzt. Dabei muss sicher .. warning:: Das Intervall darf nicht zu klein sein. Die Dauer **MUSS** länger sein als die notwendige Zeit zur Ausführung der Python-Methode ``get_data_html()``. - diff --git a/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst b/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst index f0b1280570..a3dac70967 100644 --- a/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst +++ b/doc/user/source/entwicklung/plugins/webinterface_filling_webinterface.rst @@ -99,17 +99,17 @@ Die folgenden Schritte dienen dazu, das Webinterface mit Leben zu füllen: .. code-block:: HTML - + 4. Das Logo oben links auf der Seite wird automatisch durch das Logo des konfigurierten Plugin-Typs ersetzt. Wenn das Webinterface ein eigenes Logo mitbringen soll, muss das entsprechende Bild im Verzeichnis ``webif/static/img`` mit dem Namen ``plugin_logo`` abgelegt sein. Die zulässigen Dateiformate sind **.png**, **.jpg** oder **.svg**. Dabei sollte die Größe der Bilddatei die Größe des angezeigten Logos (derzeit ca. 180x150 Pixel) nicht überschreiten, um unnötige Datenübertragungen zu vermeiden. From 5aac993af211bcd406222cb6abbd98bb4733f2f4 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 7 Feb 2022 02:35:57 +0100 Subject: [PATCH 75/95] datatables: improve defaults declaration --- .../http/webif/gstatic/datatables/datatables.defaults.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/http/webif/gstatic/datatables/datatables.defaults.js b/modules/http/webif/gstatic/datatables/datatables.defaults.js index b53cd3d60b..135072cb2f 100644 --- a/modules/http/webif/gstatic/datatables/datatables.defaults.js +++ b/modules/http/webif/gstatic/datatables/datatables.defaults.js @@ -22,18 +22,20 @@ $(window).bind('datatables_defaults', function() { }; try { + // Set datatable useful defaults $.extend( $.fn.dataTable.defaults, { lengthMenu: [ [25, 50, 100, -1], [25, 50, 100, "All"] ], // pagination menu pageLength: 100, // default to "all" pagingType: "full_numbers", // include first and last in pagination menu colReorder: true, // enable colomn reorder by drag and drop + columnDefs: [{ targets: '_all', className: 'truncate' }], fixedHeader: {header: true, // header will always be visible on top of page when scrolling headerOffset: $('#webif-navbar').outerHeight() + $('#webif-tabs').outerHeight()}, autoWidth: false, - initComplete: function () {$('#maintable').show();}, // might want to show table only after init + initComplete: function () {$(this).show();}, // show table (only) after init responsive: {details: {renderer: $.fn.dataTable.Responsive.renderer.listHiddenNodes()}}, //makes it possible to update columns even if they are not shown as columns (but as collapsable items) - "fnDrawCallback": function(oSettings) { // hide pagination if not needed + fnDrawCallback: function(oSettings) { // hide pagination if not needed if (oSettings._iDisplayLength > oSettings.fnRecordsDisplay() || oSettings._iDisplayLength == -1) { $(oSettings.nTableWrapper).find('.dataTables_paginate').hide(); } else { From 3e910d9af0b21d11ad17212a1f0763bf81832256 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 7 Feb 2022 02:36:45 +0100 Subject: [PATCH 76/95] http module webif: add truncate class to truncate extra long table cell content (automatically when using datatables) --- modules/http/webif/gstatic/css/smarthomeng.css | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/http/webif/gstatic/css/smarthomeng.css b/modules/http/webif/gstatic/css/smarthomeng.css index f37adc4358..4880eedc6e 100644 --- a/modules/http/webif/gstatic/css/smarthomeng.css +++ b/modules/http/webif/gstatic/css/smarthomeng.css @@ -31,6 +31,15 @@ plusIconlist-group-item.py-2.node-tree.node-selected { background-color: #ecf3f8; } +table .truncate { + overflow: hidden; + text-overflow: ellipsis; +} + +table .notruncate { + overflow: visible; +} + .minusIcon { position: relative; display: inline-block; @@ -247,4 +256,3 @@ a.text-shng:focus, a.text-shng:hover { color: #4d4d4d; font-weight: bold; } - From 4b3735c4d0fe6b98b13426d38e6ba202baa86e0a Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 7 Feb 2022 02:37:11 +0100 Subject: [PATCH 77/95] slightly update example plugin webif index.html --- dev/sample_plugin/webif/templates/index.html | 36 +++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/dev/sample_plugin/webif/templates/index.html b/dev/sample_plugin/webif/templates/index.html index 0a78729841..a18d4aa19c 100644 --- a/dev/sample_plugin/webif/templates/index.html +++ b/dev/sample_plugin/webif/templates/index.html @@ -5,6 +5,17 @@ {% set update_interval = 0 %} + +{% block pluginstyles %} + +{% endblock pluginstyles %} + @@ -14,10 +25,9 @@ if (dataSet === 'devices_info' || dataSet === null) { var objResponse = JSON.parse(response); myProto = document.getElementById(dataSet); - for (var device in objResponse) { + for (item in objResponse) { /* - shngInsertText (device+'_source', objResponse[device]['source']); - shngInsertText (device+'_powerState', objResponse[device]['powerState']); + shngInsertText (item+'_value', objResponse[item]['value'], 'maintable'); */ } } @@ -33,6 +43,7 @@ loading defaults from /modules/http/webif/gstatic/datatables/datatables.defaults.js You can copy that file, put it in your plugin directory, rename the "bind" function and trigger that function here instead of datatables_defaults if you want to change the behaviour. + Of course you can also overwrite defaults by putting the option declarations in {} below. */ $(window).trigger('datatables_defaults'); try @@ -113,30 +124,21 @@ {% block bodytab1 %}
      {{ _('Hier kommt der Inhalt des Webinterfaces hin.') }} - + + - + {% for item in p.get_items() %} {% if p.has_iattr(item.conf, '') %} - - + + {% endif %} {% endfor %} From 97c06a6f3c039f92c4c454d706d9e188fa3c81c4 Mon Sep 17 00:00:00 2001 From: msinn Date: Fri, 11 Feb 2022 20:24:03 +0100 Subject: [PATCH 78/95] Updated release notes --- doc/user/source/release/1_9_x.rst | 55 +++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/doc/user/source/release/1_9_x.rst b/doc/user/source/release/1_9_x.rst index cafe0fc9ae..2159c6d059 100644 --- a/doc/user/source/release/1_9_x.rst +++ b/doc/user/source/release/1_9_x.rst @@ -8,10 +8,10 @@ Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. Diese Release Notes sind ein Arbeitsstand. - - Berücksichtigt sind Commits im smarthome Repository bis incl. 30. Januar 2022 - (lib.scheduler: Removed some development logging) - - Berücksichtigt sind Commits im plugins Repository bis incl. 30. Januar 2022 - (uzsu plugin: use sun and moon objects of sh and new orb.py) + - Berücksichtigt sind Commits im smarthome Repository bis incl. 4. Februar 2022 + (Merge pull request #467 from onkelandy/datatables Datatables update and fixes) + - Berücksichtigt sind Commits im plugins Repository bis incl. 10. Februar 2022 + (Sonos: Update SoCo base framework to version 0.26.1) Überblick @@ -87,6 +87,9 @@ Updates to the CORE value has not changed) * Get relative item path: Fix issue where last character of expression part got truncated and therefore eval expression might get invalid + * Fix for eval expressions, if crontab attribute is given with an init value, which is to be used in a + numerical calculation + * Fix for non numeric values on attribute crontab: init = xyz * lib.network: @@ -96,6 +99,10 @@ Updates to the CORE * Fix validate_inet_addr return for invalid ip * Fix for clean shutdown if not connected * Added __str__ methods to simplyfy logging + * Modified startup/connect logging + * Modified __running initialization + * Added more debug logging, modified connect_on_send behaviour to respect autoreconnect setting + * Added autoconnect parameter * lib.orb: @@ -110,6 +117,7 @@ Updates to the CORE * Implemented 'raise LeaveLogic()' for logics * Small modification for executing logics * Added more details to 'update_by' and 'change_by' on items + * Bugfix for 'str' object has no attribute 'get' (line 643) * lib.shpypi: @@ -123,13 +131,21 @@ Updates to the CORE * Fix a triggertimes with empty params or wrong parameter set size * Enhance debug message 'eventtime is earlier than starttime' + * Fix a bug in relation to sunset with offset * Modules: + * admin: + + * eval syntax checker sets 'value' to the 'path relative to' item, if it is specified + * http: * Added datatables for use in webinterfaces of plugins + * datatables: Fix table width in tabs + * datatables: Improve defaults and fix some issues + * datatables: update to 1.11.4 * websocket: @@ -160,6 +176,12 @@ For details of the changes of the individual plugins, please refer to the docume Plugin Updates -------------- +* avdevice: + + * Fix web interface logging (introduces with last commit), remove very old versions + +* casambi: Resend commands on transmission error (e.g. due to sigpipe error) once + * database: * Prevent multiple threads for handling database_maxage @@ -190,14 +212,21 @@ Plugin Updates * Fix error handling and log on startup * Slight improvements in webif concerning datatables + * Update web interface (datatables, realtime values), convert debug messages to f-strings + +* knx: + + * Only execute update_item if plugin is running + * Bumped version to 1.8.0 * modbus_tcp: * slaveUnit zu Items hinzugefügt * slaveUnit-Bug _regToRead-key (adress.unit) - * bumped version to 1.0.3 + * Bumped version to 1.0.3 * Bugfix in web interface * Fixes in example.yaml + * Bumped version to 1.0.5 * openweathermap: @@ -220,15 +249,21 @@ Plugin Updates * Copy info block.html from _templates dir (for sv 3.2 and up) +* snap7_logo: + + * Unspecified updates + * Update to 1.6.0 + * sonos: * Fix for use with python 3.9 upwards (Thread::isAlive deprecated) - * Update underlying SoCo library to V_0.25.3 + * Update SoCo base framework to version 0.26.1 * stateengine: * Add gitignore to webif/visualizations folder to make sure folder is created and files in folder are ignored * Minor webif update + * Update web interface in real-time and include datatables * telegram: @@ -239,11 +274,19 @@ Plugin Updates * uzsu: * Use sun and moon objects of sh and new orb.py + * Remove uzsu\_ from scheduler name + * Simplify requirements file: scipy 1.1.0 if python >= 3.7 + * Major web interface update. Clean code, use automatic update of values, implement datatables, etc. + * Removed old uzsu_sun residuum * viessmann: * Fixed log call +* withings_health: + + * Updated requirements so that with 2.4.0 of pypi package auth is working again (hopefully) + Outdated Plugins ---------------- From 6d5d627ef31e903c44c541434f74530e2f353835 Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 12 Feb 2022 14:04:50 +0100 Subject: [PATCH 79/95] lib.metadata: Added attribute prefix 'my_' for user defined attributes (for logics, etc.) --- lib/metadata.py | 9 +++++++++ lib/plugin.py | 1 + 2 files changed, 10 insertions(+) diff --git a/lib/metadata.py b/lib/metadata.py index 399118b2f6..f33f74bed1 100644 --- a/lib/metadata.py +++ b/lib/metadata.py @@ -170,6 +170,15 @@ def __init__(self, sh, addon_name, addon_type, classpath=''): # build dict for checking of item attributes and their values if self.itemprefixdefinitions is not None: + # dummy 'my_' prefix for user's attributes for logics, etc. (add only, if list is still empty) + if all_itemprefixdefinitions == {}: + prefix_name = 'my_' + all_itemprefixdefinitions[prefix_name] = {'type': 'foo', 'description': {'de': 'Attribute für verschiedene Tests', 'en': 'Attributes for various tests'}, 'listtype': ['foo'], 'listlen': 0, '_addon_name': 'priv_develop', '_addon_type': 'plugin', '_name': 'my_', '_type': 'prefix'} + all_itemprefixdefinitions[prefix_name]['_addon_name'] = 'lib_metadata' + all_itemprefixdefinitions[prefix_name]['_addon_type'] = 'plugin' + all_itemprefixdefinitions[prefix_name]['_name'] = prefix_name + all_itemprefixdefinitions[prefix_name]['_type'] = 'prefix' + # add all prefixes loaded from metadate of the plugin for prefix_name in self.itemprefixdefinitions: all_itemprefixdefinitions[prefix_name] = self.itemprefixdefinitions[prefix_name] all_itemprefixdefinitions[prefix_name]['_addon_name'] = self._addon_name diff --git a/lib/plugin.py b/lib/plugin.py index e1ac87abae..c5e773540e 100644 --- a/lib/plugin.py +++ b/lib/plugin.py @@ -132,6 +132,7 @@ def __init__(self, smarthome, configfile): if item_attribute_prefixes is not None: attribute_prefixes_keys = list(item_attribute_prefixes.keys()) for attribute_prefix in attribute_prefixes_keys: + #logger.warning(f"plugin_name={plugin_name}, attribute_prefix={attribute_prefix}, item_attribute_prefixes[attribute_prefix]={item_attribute_prefixes[attribute_prefix]}") self._sh.items.add_plugin_attribute_prefix(plugin_name, attribute_prefix, item_attribute_prefixes[attribute_prefix]) # Test if plugin defines item structs From 3d248785aa493f62adc2e3566b28e147fb59ce56 Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 12 Feb 2022 14:53:43 +0100 Subject: [PATCH 80/95] modules.mqtt: Fixed _get_caller_type for logics (see issue #450) --- modules/mqtt/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/mqtt/__init__.py b/modules/mqtt/__init__.py index 587d6b6a5a..c40a586e5b 100644 --- a/modules/mqtt/__init__.py +++ b/modules/mqtt/__init__.py @@ -169,7 +169,7 @@ def __init__(self, sh): # return pass - + def start(self): """ This method starts the mqtt module @@ -530,7 +530,7 @@ def _on_mqtt_message(self, client, userdata, message): with self._subscribed_topics_lock: subscibed_topics = list(self._subscribed_topics.keys()) - + # look for subscriptions to the received topic subscription_found = False for topic in subscibed_topics: @@ -720,12 +720,12 @@ def _get_caller_type(self): """ caller = inspect.stack()[2][1] split = caller.split('/') - self.logger.debug("_get_caller_type: inspect.stack()[2][1] = '{}', split = {}".format(caller, split)) + self.logger.warning("_get_caller_type: inspect.stack()[2][1] = '{}', split = {}".format(caller, split)) if split[-3] == 'lib' and split[-2] == 'model': source_type = 'Plugin' elif split[-3] == 'plugins': source_type = 'Plugin' - elif split[-3] == 'logics': + elif split[-2] == 'logics': source_type = 'Logic' else: source_type = 'Unknown' From 5d58ff7753a71af2ec5389c3f0a3ecd322e8ed3b Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 12 Feb 2022 14:57:23 +0100 Subject: [PATCH 81/95] modules.mqtt: Fixed loglevel --- modules/mqtt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mqtt/__init__.py b/modules/mqtt/__init__.py index c40a586e5b..d8d29d7a37 100644 --- a/modules/mqtt/__init__.py +++ b/modules/mqtt/__init__.py @@ -720,7 +720,7 @@ def _get_caller_type(self): """ caller = inspect.stack()[2][1] split = caller.split('/') - self.logger.warning("_get_caller_type: inspect.stack()[2][1] = '{}', split = {}".format(caller, split)) + self.logger.debug("_get_caller_type: inspect.stack()[2][1] = '{}', split = {}".format(caller, split)) if split[-3] == 'lib' and split[-2] == 'model': source_type = 'Plugin' elif split[-3] == 'plugins': From 2dd6cebf50a200588157e543e58e8ca389cefcce Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 12 Feb 2022 17:11:59 +0100 Subject: [PATCH 82/95] bin.smarthome: Fixed bug when reintroducing -r option to command --- bin/smarthome.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/smarthome.py b/bin/smarthome.py index d9ec042150..849fc6c107 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -134,7 +134,8 @@ exit(0) try: - p = subprocess.Popen(command + ' -r', shell=True) + command[1] += ' -r' + p = subprocess.Popen(command, shell=True) except subprocess.SubprocessError as e: print("Restart command '{}' failed with error {}".format(command,e)) time.sleep(10) From 428b3d10a88b2f48265bb6422705049727dc96f5 Mon Sep 17 00:00:00 2001 From: msinn Date: Sat, 12 Feb 2022 17:20:32 +0100 Subject: [PATCH 83/95] bin.smarthome: Fixed bug when reintroducing -r option to command --- bin/smarthome.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/smarthome.py b/bin/smarthome.py index 849fc6c107..6c79ba7998 100644 --- a/bin/smarthome.py +++ b/bin/smarthome.py @@ -134,7 +134,7 @@ exit(0) try: - command[1] += ' -r' + command.append('-r') p = subprocess.Popen(command, shell=True) except subprocess.SubprocessError as e: print("Restart command '{}' failed with error {}".format(command,e)) From 21eee2401be9c2303e0af46797db5b0f91e5a0d7 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 09:58:38 +0100 Subject: [PATCH 84/95] Updated release notes --- doc/user/source/release/1_9_x.rst | 51 +++++++++++++++++++------------ 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/doc/user/source/release/1_9_x.rst b/doc/user/source/release/1_9_x.rst index 2159c6d059..10e1c8f47b 100644 --- a/doc/user/source/release/1_9_x.rst +++ b/doc/user/source/release/1_9_x.rst @@ -1,5 +1,5 @@ ============================ -Release 1.9.x - tt. mmm 2022 +Release 1.9.x - tt. Feb 2022 ============================ Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. @@ -8,10 +8,10 @@ Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. Diese Release Notes sind ein Arbeitsstand. - - Berücksichtigt sind Commits im smarthome Repository bis incl. 4. Februar 2022 - (Merge pull request #467 from onkelandy/datatables Datatables update and fixes) - - Berücksichtigt sind Commits im plugins Repository bis incl. 10. Februar 2022 - (Sonos: Update SoCo base framework to version 0.26.1) + - Berücksichtigt sind Commits im smarthome Repository bis incl. 12. Februar 2022 + (. . .) + - Berücksichtigt sind Commits im plugins Repository bis incl. 12. Februar 2022 + (. . .) Überblick @@ -64,12 +64,8 @@ Updates to the CORE * Fixed initialization for cases where logging.yaml contains instructions that thow an exception when SmartHomeNG is not fully configured (requirements installed) * If logging.yaml gives an exception, logging.yaml.default is used +* Changed restart handling, when in debug- or foreground-mode -* ... - -* Items: - - * ... * Logics: @@ -91,6 +87,10 @@ Updates to the CORE numerical calculation * Fix for non numeric values on attribute crontab: init = xyz + * lib.metadata: + + * Added attribute prefix 'my_' for user defined attributes (for logics, etc.) + * lib.network: * Fix for udp server eventloop too late @@ -147,19 +147,13 @@ Updates to the CORE * datatables: Improve defaults and fix some issues * datatables: update to 1.11.4 + * mqtt: Fixed _get_caller_type for logics (see issue #450) + * websocket: * Removed "untested" warning from trigger-logic command * Loglevel for 'no close frame received or sent' reduced to INFO -* Plugins: - - * ... - -* tests: - - * ... - Änderungen bei Plugins ====================== @@ -180,6 +174,10 @@ Plugin Updates * Fix web interface logging (introduces with last commit), remove very old versions +* asterisk: + + * Check connected before calling close in stop + * casambi: Resend commands on transmission error (e.g. due to sigpipe error) once * database: @@ -218,15 +216,20 @@ Plugin Updates * Only execute update_item if plugin is running * Bumped version to 1.8.0 + * Fix connected bug for init ga * modbus_tcp: * slaveUnit zu Items hinzugefügt * slaveUnit-Bug _regToRead-key (adress.unit) - * Bumped version to 1.0.3 * Bugfix in web interface * Fixes in example.yaml - * Bumped version to 1.0.5 + * Feature to write to registers + * Bumped version to 1.0.6 + +* mpd: + + * Fix connection related bug, remove scheduler at stop * openweathermap: @@ -265,6 +268,14 @@ Plugin Updates * Minor webif update * Update web interface in real-time and include datatables +* tasmota: + + * Implemented support if TW light sources + * Implemented ZIGBEE support; + * Updated Docu + * Updated WebIF + * Bumped version to 1.2.0 + * telegram: * Fix web interface (using default template) From b23afbad474105041821cd58156c5afbab1aaef7 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 11:10:04 +0100 Subject: [PATCH 85/95] Updated release notes --- doc/user/source/release/1_9_x.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/user/source/release/1_9_x.rst b/doc/user/source/release/1_9_x.rst index 10e1c8f47b..e2eae35814 100644 --- a/doc/user/source/release/1_9_x.rst +++ b/doc/user/source/release/1_9_x.rst @@ -89,7 +89,7 @@ Updates to the CORE * lib.metadata: - * Added attribute prefix 'my_' for user defined attributes (for logics, etc.) + * Added attribute prefix 'my\_' for user defined attributes (for logics, etc.) * lib.network: @@ -178,6 +178,19 @@ Plugin Updates * Check connected before calling close in stop +* avm: + + * Created _pv_1_5_12 before merging PR + * Connect to Smarthome Devices via AHA-Interface (tested with Fritz 440, Comet Dect) + (therefore Plugin AVM smarthome can be depreciated) + * Implemented call deflections (tested with Fritzbox 7530) + * Renamed parameter "index" to "avm_tam_index" + * Code cleanup (add new functions to minimize code repetitions) + * Improved error handling + * Updated WebIF + * Updated documentation + * Bumped version to 1.6.0 + * casambi: Resend commands on transmission error (e.g. due to sigpipe error) once * database: From 4e1ee82e12e1cd95d8be90d11f11a84dbadc1bbc Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 12:21:00 +0100 Subject: [PATCH 86/95] lib.shtime: Added info logging for upcoming tz chnges --- lib/shtime.py | 16 ++++++++++++---- lib/smarthome.py | 10 +++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/shtime.py b/lib/shtime.py index b9a15482f4..0dfaf9b400 100644 --- a/lib/shtime.py +++ b/lib/shtime.py @@ -124,8 +124,12 @@ def translate(self, txt, vars=None): def set_tz(self, tz): """ set timezone info from name of timezone + + :param tz: Name of the timezone (like 'Europe/Berlin') + :type: tz: str """ tzinfo = dateutil.tz.gettz(tz) + self.logger.info(f"set_tz: tz={tz} -> tzinfo={tzinfo}") if tzinfo is not None: # TZ = tzinfo self._tz = tz @@ -136,12 +140,16 @@ def set_tz(self, tz): else: self.logger.warning(self.translate("Problem parsing timezone '{tz}' - Using UTC").format(tz=tz)) self._timezone = pytz.timezone("UTC") + self.logger.info(f"self.set_tz: -> self._timezone={self._timezone}") + self.logger.info(f"self.set_tz: -> self._tzinfo={self._tzinfo}") return def set_tzinfo(self, tzinfo): """ Set the timezone info + + :param tzinfo: """ self._tzinfo = tzinfo return @@ -168,7 +176,7 @@ def tz(self): """ Returns the the actual local timezone - :return: Timezone + :return: Name of the timezone (like 'Europe/Berlin', or 'UTC' if not set) :rtype: str """ @@ -190,7 +198,7 @@ def tzname(self): """ Returns the name about the actual local timezone (e.g. CET) - :return: Timezone info + :return: timezone string (like: 'CET') :rtype: str """ @@ -201,7 +209,7 @@ def tznameST(self): """ Returns the name for Standard Time in the local timezone (e.g. CET) - :return: Timezone info + :return: Timezone info (like: 'CET') :rtype: str """ @@ -213,7 +221,7 @@ def tznameDST(self): """ Returns the name for Daylight Saving Time (DST) in the local timezone (e.g. CEST) - :return: Timezone info + :return: Timezone info (like: 'CEST') :rtype: str """ diff --git a/lib/smarthome.py b/lib/smarthome.py index 2680e5a2c7..c67f709c5b 100644 --- a/lib/smarthome.py +++ b/lib/smarthome.py @@ -264,9 +264,11 @@ def __init__(self, MODE, extern_conf_dir=''): ############################################################# # Setting (local) tz if set in smarthome.yaml + # (to ensure propper logging) if hasattr(self, '_tz'): self.shtime.set_tz(self._tz) - del(self._tz) + # self._tz is deleted later (work in progress) + #del(self._tz) ############################################################# # test if needed Python packages are installed @@ -288,6 +290,12 @@ def __init__(self, MODE, extern_conf_dir=''): self.shng_status = {'code': 1, 'text': 'Initalizing: Logging initalized'} + if hasattr(self, '_tz'): + # set _tz again (now with logging enabled), + # so that shtime.set_tz can produce log output + self.shtime.set_tz(self._tz) + del(self._tz) + ############################################################# # Fork process and write pidfile if MODE == 'default': From 5f3785d683bcdedfdaf0de3ad76bd0e13af351a3 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 12:22:07 +0100 Subject: [PATCH 87/95] tools/plugin_metadata_checker.py: Added two checks for plugin name (length and lowercase) --- tools/plugin_metadata_checker.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/plugin_metadata_checker.py b/tools/plugin_metadata_checker.py index 95c66160ff..b72e9e9be9 100644 --- a/tools/plugin_metadata_checker.py +++ b/tools/plugin_metadata_checker.py @@ -32,7 +32,7 @@ import os import argparse -VERSION = '1.7.6' +VERSION = '1.7.7' print('') print(os.path.basename(__file__) + ' v' + VERSION + ' - Checks the care status of plugin metadata') @@ -600,6 +600,13 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis print("Check metadata of {} plugin '{}'".format(plg_type, plg)) print() + # Checking plugin name + maxlen = 12 + if len(plg) > maxlen: + disp_warning(f"A plugin name should not be longer than {maxlen} characters. {plg[0:12]}", f"The plugin name '{plg}' is {len(plg)} characters long") + if plg != plg.lower(): + disp_error(f"Invalid plugin name '{plg}'.", f"Plugin names have to be lower case. Use '{plg.lower()}' instead") + # Checking global metadata if metadata.get('plugin', None) == None: disp_error("No global metadata defined", "Make sure to create a section 'plugin' and fill it with the necessary entries", "Take a look at https://www.smarthomeng.de/developer/development_plugin/plugin_metadata.html") @@ -621,8 +628,8 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis else: if metadata['plugin'].get('state', None) == None: disp_error('No development state given for the plugin', "Add 'state:' to the plugin section and set it to one of the following values ['develop', 'ready', 'qa-passed']", "The state'qa-passed' should only be set by the shNG core team") - elif not metadata['plugin'].get('state', None) in ['qa-passed', 'ready', 'develop', '-']: - disp_error('An invalid development state is given for the plugin', "Set'state:' to one of the followind valid values ['develop', 'ready', 'qa-passed']", "The state'qa-passed' should only be set by the shNG core team") + elif not metadata['plugin'].get('state', None) in ['qa-passed', 'ready', 'develop', 'deprecated', '-']: + disp_error('An invalid development state is given for the plugin', "Set'state:' to one of the followind valid values ['develop', 'ready', 'qa-passed', 'deprecated']", "The state'qa-passed' should only be set by the shNG core team") if metadata['plugin'].get('multi_instance', None) == None: disp_warning('It is not documented if wether the plugin is multi-instance capable or not', "Add 'multi_instance:' to the plugin section") From 057f51d535b4b3c45c6f7a413889b7c5304e526a Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 12:39:05 +0100 Subject: [PATCH 88/95] tools/plugin_metadata_checker.py: Fixed check for mandatory value given and default value given --- tools/plugin_metadata_checker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/plugin_metadata_checker.py b/tools/plugin_metadata_checker.py index b72e9e9be9..0481ce7921 100644 --- a/tools/plugin_metadata_checker.py +++ b/tools/plugin_metadata_checker.py @@ -32,7 +32,7 @@ import os import argparse -VERSION = '1.7.7' +VERSION = '1.7.8' print('') print(os.path.basename(__file__) + ' v' + VERSION + ' - Checks the care status of plugin metadata') @@ -680,7 +680,7 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis if not is_dict(par_dict): disp_error("Definition of parameter '{}' is not a dict".format(par), '') else: - if par_dict.get('mandatory', None) != None and par_dict.get('default', None) != None: + if par_dict.get('mandatory', False) != False and par_dict.get('default', None) != None: disp_error("parameter '{}': mandatory and default cannot be used together".format(par), "If mandatory and a default value are specified togeather, mandatory has no effect, since a value for the parameter is already specified (the default value).") test_description('parameter', par, par_dict.get('description', None)) @@ -691,7 +691,7 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis if not is_dict(par_dict): disp_error("Definition of item_attribute '{}' is not a dict".format(par), '') else: - if par_dict.get('mandatory', None) != None and par_dict.get('default', None) != None: + if par_dict.get('mandatory', False) != False and par_dict.get('default', None) != None: disp_error("item '{}': mandatory and default cannot be used together".format(par), "If mandatory and a default value are specified togeather, mandatory has no effect, since a value for the parameter is already specified (the default value).") test_description('item attribute', par, par_dict.get('description', None)) From 40028c3ad19ff1a559d7d8ab333953d87df19922 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 13:26:08 +0100 Subject: [PATCH 89/95] Documentation: Removed old (and incorrect) information about item change logging --- doc/user/source/konfiguration/logging.rst | 28 +++-------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/doc/user/source/konfiguration/logging.rst b/doc/user/source/konfiguration/logging.rst index 5db8f07251..c1cbb17366 100644 --- a/doc/user/source/konfiguration/logging.rst +++ b/doc/user/source/konfiguration/logging.rst @@ -203,31 +203,9 @@ muss in der config auch dieser Name verwendet werden. Ohne `plugin.` oder `logic Logging der Veränderung von Items --------------------------------- -Die Veränderung von Item Werten kann am einfachsten geloggt werden, indem bei dem Item das Attribut **log_change** gesetzt -wird und auf einen entsprechenden Item Logger verweist. Der Item Logger muss in der etc/logging.yaml mit Level INFO oder -DEBUG definiert sein. - -.. code-block:: yaml - :caption: items/items.yaml - - test: - item: - log_change: - - -und - -.. code-block:: yaml - :caption: etc/logging.yaml - - ...- - - logger: - items_: - level: INFO - handlers: [shng_details_file] - - ... +Ab Version 1.5 von SmartHomeNG erfolgt die Konfiguration des Loggings der Veränderung von Item Werten über +Standard Item Attribute, wie es :doc:`bei den Standardattributen ` +beschrieben ist. Best Practices From 1becf3e1a8cd1b8c70ecd2f374cfab0ae026dd28 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 13:47:46 +0100 Subject: [PATCH 90/95] Release notes updates --- doc/user/source/konfiguration/logging.rst | 6 ++++++ doc/user/source/release/1_9_x.rst | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/user/source/konfiguration/logging.rst b/doc/user/source/konfiguration/logging.rst index c1cbb17366..0409c2e646 100644 --- a/doc/user/source/konfiguration/logging.rst +++ b/doc/user/source/konfiguration/logging.rst @@ -180,6 +180,9 @@ in der `__init__` Methode instanziert werden. Das ist inzwischen nicht mehr notw Klasse erzeugt den Logger inzwischen selbst. Ein **import logging** ist nicht mehr notwendig und die Initialisierung des Loggers in der `__init__` Methode sollte auch weggelassen werden. +Der Logger konn/muss in der ``etc/logging,yaml`` konfiguriert werden. Der Name des Loggers ist ``plugins.``. + + Für die Entwickler von Logiken: Verwendet man zur Instanziierung einen eigenen Namen (nicht empfohlen), wie z.B. @@ -198,6 +201,9 @@ muss in der config auch dieser Name verwendet werden. Ohne `plugin.` oder `logic DWD: level: DEBUG +Standardmäßig haben Logiken bereits einen Logger, der in der ``etc/logging,yaml`` konfiguriert werden kann/muss. +Der Name des Loggers ist ``logics.``, wobei der Name der Logik, der in der Konfiguration festgelegte +Name ist und nicht der Name des Python Skriptes. Logging der Veränderung von Items diff --git a/doc/user/source/release/1_9_x.rst b/doc/user/source/release/1_9_x.rst index e2eae35814..a73c8fb744 100644 --- a/doc/user/source/release/1_9_x.rst +++ b/doc/user/source/release/1_9_x.rst @@ -369,7 +369,10 @@ Weitere Änderungen Tools ----- -* ... +* plugin_metadata_checker.py: + + * Added two checks for plugin name (length and lowercase) + * Fixed check for mandatory value given and default value given Documentation From 22fc3a5b385398ebad98508f88fae3bfa3b9bb73 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 15:13:26 +0100 Subject: [PATCH 91/95] Modified .gitignore for documentation files --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 3b655c6854..de3ae3333c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,14 @@ Thumbs.db # Don't check in built documentation /doc/user/build /doc/user/source/plugins_doc +!/doc/user/source/plugins_doc/plugins_footer.rst +!/doc/user/source/plugins_doc/plugins_gateway_header.rst +!/doc/user/source/plugins_doc/plugins_interface_header.rst +!/doc/user/source/plugins_doc/plugins_protocol_header.rst +!/doc/user/source/plugins_doc/plugins_system_header.rst +!/doc/user/source/plugins_doc/plugins_unclassified_header.rst +!/doc/user/source/plugins_doc/plugins_all_header.rst + /doc/developer/build /doc/developer/source/plugins_doc From 38918446638ed74fca0cf55cba1bb7733d91d0c1 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 19:25:12 +0100 Subject: [PATCH 92/95] Documentation updates; New page 'All Plugins' --- doc/build_plugin_config_files.py | 1 + doc/build_plugin_rst_files.py | 51 ++++++++++++++++-- doc/user/source/_static/img/all.png | Bin 0 -> 27830 bytes doc/user/source/plugins_all.rst | 7 +-- .../plugins_doc/plugins_system_header.rst | 2 - doc/user/source/release/1_9_x.rst | 8 +-- tools/plugin_metadata_checker.py | 2 +- 7 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 doc/user/source/_static/img/all.png diff --git a/doc/build_plugin_config_files.py b/doc/build_plugin_config_files.py index a336786df8..0bc017e4ab 100644 --- a/doc/build_plugin_config_files.py +++ b/doc/build_plugin_config_files.py @@ -736,6 +736,7 @@ def write_configfile(plg, configfile_dir, language='de'): global docu_type docu_type = start_dir.split('/')[-1:][0] # developer / user + print('Creating the configuration documentation pages for the plugins') print('Start directory = '+start_dir) print('Documentation type = '+docu_type) print('Documentation language = '+language) diff --git a/doc/build_plugin_rst_files.py b/doc/build_plugin_rst_files.py index 568fbaf411..37ace6267d 100644 --- a/doc/build_plugin_rst_files.py +++ b/doc/build_plugin_rst_files.py @@ -75,8 +75,9 @@ ['interface', 'Interface', 'Interface'], ['protocol', 'Protocol', 'Protokoll'], ['system', 'System', 'System'], + ['web', 'Web/Cloud', 'Web/Cloud'], [type_unclassified, 'Non classified', 'nicht klassifizierte'], - ['web', 'Web/Cloud', 'Web/Cloud'] + ['all', 'All plugins', 'Alle Plugins'] ] def bold(s): @@ -153,6 +154,16 @@ def get_version(section_dict, maxlen=8): return lines +def get_state(section_dict, maxlen=20): + state = section_dict.get('state', '') + + #import textwrap + #lines = textwrap.wrap(state, maxlen, break_long_words=False) + #if lines == []: + # lines.append('') + #return lines + return state + def get_maintainer(section_dict, maxlen=20): maint = section_dict.get('maintainer', '') @@ -230,6 +241,7 @@ def build_pluginlist( plugin_type='all' ): plg_dict['name'] = metaplugin.lower() plg_dict['version'] = get_version(section_dict) plg_dict['type'] = plgtype + plg_dict['state'] = get_state(section_dict) plg_dict['desc'] = get_description(section_dict, 85, language) plg_dict['maint'] = get_maintainer(section_dict, 15) plg_dict['test'] = get_tester(section_dict, 15) @@ -237,6 +249,7 @@ def build_pluginlist( plugin_type='all' ): plg_dict['sup'] = html_escape(section_dict.get('support', '')) else: plgtype = type_unclassified + plg_dict['type'] = plgtype else: plgtype = type_unclassified if plugin_type == type_unclassified: @@ -285,7 +298,7 @@ def build_pluginlist( plugin_type='all' ): return result -def write_rstfile(plgtype='All', plgtype_print='', heading=''): +def write_rstfile(plgtype='all', plgtype_print='', heading=''): """ Create a .rst file for each plugin category """ @@ -314,7 +327,10 @@ def write_rstfile(plgtype='All', plgtype_print='', heading=''): fh.write('\n') if (len(plglist) == 0): - fh.write('At the moments there are no plugins that have not been classified.\n') + if language == 'de': + fh.write('Zurzeit gibt es keine noch nicht klassifizierten Plugins.\n') + else: + fh.write('At the moments there are no plugins that have not been classified.\n') else: if not fh_dummy_used: fh_dummy = open(plugin_rst_dir + '/' + rst_dummyname, "w") @@ -372,9 +388,10 @@ def write_rstfile(plgtype='All', plgtype_print='', heading=''): fh.write('\n') fh.write(' +-'+ '-'*65 +'-+-' + '-'*8 +'-+-' + '-'*165 +'-+-----------------+-----------------+\n') if language == 'de': - fh.write(' | {p:<65.65} | Version | {b:<165.165} | Maintainer | Tester |\n'.format(p='Plugin', b='Beschreibung')) + fh.write(f" | {'Plugin (Konfiguration)':<65.65} | Version | {'Beschreibung':<165.165} | Maintainer | Tester |\n") else: - fh.write(' | {p:<65.65} | Version | {b:<165.165} | Maintainer | Tester |\n'.format(p='Plugin', b='Description')) + fh.write(f" | {'Plugin (Configuration)':<65.65} | Version | {'Description':<165.165} | Maintainer | Tester |\n") + #fh.write(' | {p:<65.65} | Version | {b:<165.165} | Maintainer | Tester |\n'.format(p='Plugin', b='Description')) fh.write(' +='+ '='*65 +'=+=' + '='*8 +'=+=' + '='*165 + '=+=================+=================+\n') for plg in plglist: plg_readme_link = ':doc:`'+plg['name']+' `' @@ -406,7 +423,31 @@ def write_rstfile(plgtype='All', plgtype_print='', heading=''): else: plg['sup'] = "`"+plg['name']+" support <"+plg['sup']+">`_" fh.write(' | {plg:<65.65} | {vers:<8.8} | - {desc:<163.163} | {maint:<15.15} | {test:<15.15} |\n'.format(plg='', vers='', desc=plg['sup'], maint='', test='')) + else: + fh.write(f" | {'':<65.65} | {'':<8.8} | {'-':<165.165} | {'':<15.15} | {'':<15.15} |\n") + + leerzeileausgegeben = False + if plgtype == 'all': + if language == 'de': + desc = 'Plugin Typ: **' + plg['type'] + '**' + else: + desc = 'Plugin type: **' + plg['type'] + '**' + leerzeileausgegeben = True + fh.write(f" | {'':<65.65} | {'':<8.8} | {'':<165.165} | {'':<15.15} | {'':<15.15} |\n") + fh.write(' | {plg:<65.65} | {vers:<8.8} | {desc:<165.165} | {maint:<15.15} | {test:<15.15} |\n'.format(plg='', vers='', desc=desc, maint='', test='')) + + if plg['state'] in ['deprecated', 'develop']: + if language == 'de': + desc = 'Plugin Status: **' + plg['state'] + '**' + else: + desc = 'Plugin Status: **' + plg['state'] + '**' + if not leerzeileausgegeben: + fh.write(f" | {'':<65.65} | {'':<8.8} | {'':<165.165} | {'':<15.15} | {'':<15.15} |\n") + fh.write(f" | {'':<65.65} | {'':<8.8} | {'':<165.165} | {'':<15.15} | {'':<15.15} |\n") + fh.write(' | {plg:<65.65} | {vers:<8.8} | {desc:<165.165} | {maint:<15.15} | {test:<15.15} |\n'.format(plg='', vers='', desc=desc, maint='', test='')) + fh.write(' +-'+ '-'*65 +'-+-' + '-'*8 + '-+-' + '-'*165 +'-+-----------------+-----------------+\n') + fh.write('\n') fh.write('\n') diff --git a/doc/user/source/_static/img/all.png b/doc/user/source/_static/img/all.png new file mode 100644 index 0000000000000000000000000000000000000000..c3ad05a451d9cc80812a081b8b9f829093484829 GIT binary patch literal 27830 zcmd3Ng;N~O7wrOzySoN=2m}f41b0~6!r~I#A-KB)0xTYMfdztFfM8+qV8MeW_y&i^ z_j^_EZ+KI8W~!&Ur)T<3ci%qeOuV+HGAzjl$o|Wu@96Df4{&mH_+ahFKs+!Tg&GwSR_`e+T z?j8YNjt*dNfV@0EgSL;2i=!=rg1f7=qZ@;Qy`7`2wYQ_Y8w1$e+e4g>&(_QSCGBf( z>+b66?)Kro>i9hVSH|vtMfd{kz1;Z(c=`E8c6x3B0OxvD1zCOnl|MaxUzwEyPM&e2UKutY-T%L= z{#M~LC%EORIL(6qlJQsk0lvxclQn_?Zkv%vP9v51SF)tk;oYX^pMj=(sTAB#DPa~Z z2v(?i|Er7?cgsiI$&SyDAtJU4=u&`9T_kVezjTyeKkIo9(px-}(zxjp?g^hmgDKYu ztWZ~pv;jbk6ud}Tj+K%A8P`Z>dO)M&5HSnCkt{~u!*^K#RyYu2T6QxGitZ`m9(Lmb zys7rqbIIy6H}Yi7gbz_LY+K2N%cS*hrURHz2{>d30BG45NvelNnVaGM3=>S@5&*6D zDX9Gb0u&YGuQ_3#1<=xPJZK3<-c?Nc@)V;Jh8tK}8`~Y<7y*L!uL!!ffHM`t*+yyB zw=qDSFN~BI1c%(M+kY$Fo$=C^)l9SOu+7OyG~(KiqNF>HMb0Bc9#Nhq>Nl=WQnx>T z>KCy?TQAd=%TUSc>T|kw|Mc}yudY%jq8;VobOp`Wytw2!C-bY6UKJKb#y3DEf&aU5 z>Os3sMt)2NrOnRo2458bUk;VVIow4Ak@Ij40`C}qyiutvr*7cAz)ccd_2GGz*R4FS zOiop&%W|C0R~kq9P8Ci8{X?iy;hI7jOxG7LJ(B8FUoddJJ$0uP>@%!oN(7HD6VAx` zvawxmcQjF!)?}{&fS+dD^ojr@#cFn~SOlNrAMjZ&zI_jk;twwe7x1_B3$E7f>hZV_ zMC_^Z_*{5gSe`tvrfisGRC*w3#YP|d#ZQXTeYa)F z+X}Pqt5ehJE2mNphig|ODd@F zAaBtL03+zF@PDO9ejRe`x#}wpaS3?)Y?>y_W&Xo`{1;Z`3ID<{GnzQ&9s#x6;Lxk4h-8f)b6+kbBC|VB9^5nDE62C1mqv0Jl=>~c=%xds=NS;qLjnp@0 z%1y?LekMSvAvhZ;%@v%DVnPaz0BDKG8|8Z6|vIJX9IiO>Qu*T>e7_ILQygDuE(uY>la*90cJiPyOw z)AHBRNl6=T7@7m<`6uSKX*FY4kf9w7{O1|e4}|gzlLMsa|>Ju@_loU9>G118{CPk8Sdf?SN<=i)Q3&P55!@< zEKd!t;Rb(*xWOvJxS8BS{Mg}N&|v_pYYch*%_;YV&n8fm^`3%uyc#^f4gf@Yl=aed z>X9>)>_`A|6Q%`15EP9^|07wAd}38GC|}LL0(@)3&45rF}~3@8UvI29BB$28k65QvF2v%B#FEb#4J8i2a{I z{qJ*M9qXLYQ+`apPxX=5KgbSp=FoL(*u2Q?Hyy+I>-xR+C>Igpdus7j@8yHQTz?B@tz7N8iZs{e4l*o z#SZeQ%Lxp)sp1RE*Tm(^K~d*{d(j?c2OBT*Lgr<_>1uG!%<1@7f&czJ*-s~`EMB~w zvwT&l@a^x^!Qu+CP4@dWDYBe!3*=9%@)H@ad%@3Xoy2_HyA%kmRztNvBq>vWQY8s= z?_KXUYUFEqbHK8>a8XFDM;4S0&KZ%6nr0H2qbe1gzB61V4#2>?$)VPeF;@vOPq|!B z@^z*~YO=MqFFaxRuW_+KzRCn2>=|f+S!1#Xm;sXz{Ps>A5v}#d?TxF$w?shE7_$6d zE+G(u1~nC*$Q*;8c_~TDQH#6IR5{xKBYAr+X)RfYu>0;lg_W<=mef?Mhm-itI5^dnr zoaL(GCXdZ4!xcQ3`qAZ0*R`qJ;&iPR{qw()PDTALKjUb_dSdmlUEA}EVmWLz&hEU0 znSpH%Tzzk;cUDHkFm7W@u2`Wz#e@Lxwn@FU9G+ldq#TYfZQOd zMM-FLS=1Ydf#ASAGK}-Cn?{rWrQO7+(6pRo{|pta#B?Oi{VLWBb>6b~Ew3{;z~Dzj zlQqGJkK|_Y;6~wAxv`L3i4>VUwOM0u(T{bjKbCP3p(jJ?yjB0#ic~-RDk{^- zP450LqPk3eT62Rg?d}MWcULFPZ0J#wB~THaKaFKj63q!8|I1d^@&yZKwQ-j~x8Uim z1L#*Pz7@0hGJ0AUc(poD`UsZnYIVccv};(Enxg_v$JKXW)}u8@OZW=nq%X$K10;-6 z#aH~U=ffsXmmHkvFr(GrHE%dg4!ozmF<(#29wYI*cIchJj>^sTZ@a%N3v5MQK}q)Z ztS(w#`2;b5X=H^U8jY+lKL#y&-{P8F$O}6^ZrKQZS`VIetGMU){oWSCxsb1@JlF`m#lxaYF|mUuG2OswWFBhgzs5=(hPHY2d{Fnr-4qBdyV$C!8ZWvp@&3{~BbBKJ>OxwvATsjM&l8v7QDioPz*37Y@?24+PI zdZ$!WH*K^r@TgwYRZ1^LGBJM7MOrP+sJYX;7*%&Lz@6u z&{Rl>k>ruep+hlE9z&yRVw1@+K6d*U%iF0Ed*oNwkUg`W`M2Nr=kYNjMCNxyo~EfG zl_ckZm8FrB*b7G2Xf|jGpnpN5lEQ6MoO*vWe;?ir|>Q!%hC71AvrS>U(~vgy}2X@tp;Gi|?N8ZEmt+ zE0cq>PQ_!tx)HJRjA-tBGGEk3V|V!0>Wbm9Kc^&ZVN;#YUpftbrd3ENl98cb>jkxjOwQjOjjDDKL7CTy8WMNlcMN*6X z{o|tGH}_sTw_+ds^0~weys-j};qv1?OP1W=` zEkc(Sg}6)Kg=YA#-&1c3f!pw5?}luu+}95~cs>l&j%%ML)so>%Mvw)iIjTt#<@)f- z=tcSyx=aS8h)rF$eGw?S(L6|`hrX&fHF+kSQ_SO27iv8fTi*MKVch+8L0HWc;BqWgt4sbylT`TKWahMen$kbwg zpg$a@x97jtQ1H1YFZ7?znoNI-yu5DM243q@$0W9}2)lBZiSf5NbGcfwbzt!3VGb+Z-g_Q{lsgdfLuxptw zE9DcNbotf;qhsHwvr7U(L$9a4{{4}<9a+(N? z+{sBhMG0i=)e@220>v0H_C2%|>59jq)YNCDc^??OrKB7{N^6>7q!YUDo^ck{@JbUI zWiBc@E$wTh^boUDlH`qT(l+{yO2Afolizm2jpkjbDEo+IyMY|sHgAu6ve7XRpX`Vr ztBA`$Kuf>lc^jMhg1Zfhh2?akhD>nyY_ItP*1Z;rl+L66Z!l)3nf~&Yj7~Z7qR)1~ zGrOU<7qhf+_-Fy0Mv~OMe$TpLwsK1~laxxX-fDu+$olF-F?ECznAS?@+ow7j>Q123I z0;Ys8<0NM#lo{Y)FqBk>zF7;%>+u#OMrm3@qUl8ZIhn>#{Z)zkwM z6b+a>t$qswM0H0kxn@F%9P-SF@<6|hRRNe9GIn)MC9&`ww!)bqej1yA*ir%?H1%wW z#1u~~p4ZrkN!t7o=mz12p?zX72KLNkG1%<=ggn=}s3NFLw5y&9HXg2Si0L)*?uYQd zchE0Z!`Ud<T^4U# z;-s<=Ra|Z3Cv?8Ql+?FVPiJt=t3+vI3Ad>$NLUfcl!;Hd3figu<9<7aF)e8WFpAS^f!W z4u*(jx3yj%1!*p*&1fTZyKd(jg7&mA2*@tnM65h%RR=!TU$)GbfIJ={wQ#w|5`zid zq6{55G(G>ccpp71mJeLumwPWN{;sGYHDW`|`l96UrV)JNN5>VW2Q6NDjYc|h!q&{f zz(+$)HrI{O^J>RTmo}JjKDvCawfQIF7gF3mi$i)1m*_iR;%|JATcdQHD1NODymV2Y zufAY1(E!|WDDV6hn=2YfzDQM+>1JHpAnlC`dhxoC*?1;8nR8uPkF{C|NJe1b4S~5n zpX=MIAS}-T+VGJnL@X=Zsr4pyIHmQ|gBq@Jm~mZf_BzN2tkW~EhUr$5Ylxa}4mOz_ zZ6dJ3maRR!gvzL3PH9u(v3e0oenf2c#Y6}* zF`umOXY*)@f{rIROwD*Yz?cDDEyT(*q%^xis^a+4>ZiHHvaY`Ildg8F{RKy^Uu!U6 z=h5B}Q-7jMPUw3kT3}(HN5~iaS}K?}kmWTo*hry8evar&+M>?h~)F%E&1vY3Cyze%m9bRlm@+qi7>>p5jO6)(eEn9}=-Il(Sy4fQLerMGAwtkdQlb4~g zei4?XoK6B5qk)Q2Dijp@J}1@Br%PzqLKal=b64v5P+)(&oo7P(2P5`V9yddldXAr^ z+8Le6VcKY;vA!PIP}q47@WWFNDHzMi;IWn|AhNc1@;{B`;?)tHMm(v@uX=TSkCl%=8 zbvlIK?K?5vdL#8OyvqmjtNy3=+^MD*j({aJNs36|M34G*afk))e!N2R<+4V+s zx>2oR&*xrJn)rLhWVfLl*De9-w-G~XL7p~y1_u%bUM7t+{f1^A`+_%>;r9y2{T+T9 z)^;|g*L|AZwl%E~fEjr#Naitq8 z(E6LggjK;}1Zqmzbk*5Bm$+d@hiz)4$6KU(By}R|bQDQ~pPb>BBHlBmZ7D4cxWq&f zJ6;2;`dJ)17quK~cVE!}(zjPAUm@$^_Uh&UrtqLNPEGlJj{V>R{Kh_8x83yguXJwW zut{F)K?Y^`5h7)~dB$(2OZtI>u5g{+DCYcW)#040M!EN{4!?*P3kMyWs)XOMx{)qn zyMeqcj|AnX*8u8SV2Eh%16B(6sk~?hAf6-%*luO?Y=1M}w;mZHS*1%f)VMAdUClkS z#P_hU-t#UugKm`zJ*x%|#b&wOS)_vf33u`7i@B$NXYC~HH^Q@dbEzdARmIgBE9oGE zz}ZgbJWH%foedIk%aC{^7Tjdh1E0tB4_&i(fGuPQLQh{`?=-GZ8sn`1qTiQldmyIwe~xycKe|gkF8w&pv>P)V z(qctD@%KjJRexDNiCn?{P(#2z>f)#J;P; zMQQ{WfoD%m1dqS|dA8YmzxwEB_QPCS_{+|150ypUHh>>HWlrjmfb4C*Q#k%?mv5vT9(aFz^KtPdLBcrtb?uw>bJtX63&j*>dprh2Y$?yfujMLJhjI&Z8IGi2gIa0oCk`%##Nt`cQCHQ;I;?Py)i%cNol1I&?U2s2 z-e8{2^ly#f9@zwENd9;@8xL30p5^ABPo`AY4|)!#*(|dBG*iS;lA(6A!c-t;3~C$q zZ$@-o#DR@*as&UAf0s*({iSv2Zr@L04AXm%NNti8GXsEU$xCaaTZTikajZ*SDh{r+U-!r=);0pK}v+AAHot1F;W3 zH{c}|Vq(PPEXf)vwXQnC3k?=W)uB$g0r51mXYaH69MHvD6i0|2ns z(v=yCQss81{bu83}YHw6Ca}lXm>8iFlLRT`$88Es0d?fm*Gx2h*^Jy@T%PXwMAanRupc4 z+4d{LE%?zne#B(bzMVu@03JX>@+sUoxuHM3R}-?A5XiA$&?7`R!Na}2fn<2k%*y5B zHx$`c`UIb4l9BsSe|ZTA|3>j)SQq@he=bnIw%XZwnusN+tP1ts`UNe=sn06P&lvEm zL8*JAT9kNJK%6@_(nJR#TSmfoxp?%w+<18}~{WB(+MzR%)Co976 zcX3UnAOAbG8}IPeCt_6OD@gB@dP6JqZ#38%7R!hoL)y~q8ZLlDwa9y(-IVd&hXoE? zJc!ArilzJu(D|2&Au1R^esBz^rLC(~JAYecKINMQu%i7YeO23$I`s2nqGvFI;~l1R zaz;X_^PAW2K1gk|84sH}6j@nSHg_IE!elKdVQ?9Wqa2Sxe2RRRRTB524V(aChIFNRCRsx2s;qW+sATMo1GL;5?& zE;N|5L6-E#lsvk936Pk50Qe5^HFOIMahH+Xjbd$N<_}m6mFPJ6{DVg)ftnGO?eI%` z$UCtKX$9Sg3jlhG5rb@*d_eb&UieAUtV*vI_F{V(~F>;2oK3l%pmm!n8q z@eH|QqsUzQY)-CP27SFqkGk2-&T_Bgnp`EVL;tNA1y0h@%JxS!@r&4k!-fqR5xWQE z@MNI6DwSK$9rbW}%?gI`)6$lR#k1^Q
    • QliB@i$EYTjh}@GUPcXf*C~LSIpeIk6 z?}?Z(7@a1Stp8Z4I&aiq)1NJI`)@%TQQy_aaaLh4JznN zfu3o5w_f0V!v?EjO^3DQ(*={;hR8>@_l9Rzx-59Rkz2Ok&XM@p1?A=Dwg`dd7{R-peWQIa z7HHce(ft#3sMf9Gga<+Yu2cj{kdHe3UYr6j3b{X#Bs>5d#~2MD!U#ZBjBU&C{(Q|&=k6-6uYAPkS*6GtGAw#?Bqxgi0pF>k&<#7v!H^ak7~>$Wewfr;+lob<4H z97pKIT`6wSYf3A9;rG~dO!-Kcl0|=3)&G0}CF(m5F@5aW$6p+!GMSbON4+*S$2j{w zgef3W-{^tq-gUy||5Md8_(-ILo70~SGuRAZbkhTqfT?2orj8L2_M<|@zZ{NQs*MQY zJ|Am5r==v!8}t19Pe~?vqyI3UZAnV~U4Jr&7UlRz+Tdj6 zg4MU4+JoJ3o`+!;@_JnqA1c8bfLNsSqzN({k%qeko5a&!8t0?Ya>B-7{`dLr@tmS@>FTIWr zAkW7(zQx@-x0vZ_CK%Pv#}5o0G)2GjLU=lwyT9*c;q{rU>e*vmDYv|fol{N;K86c1KhS4wn}faYIJe}un211UM&200QXBXtv-z%DzIMf`jia+sW&XH9*JY0jwvpa-7a z|3%?;183rupytKCZ_mk<2M;LbEdqF)0YV+mlh;L4QAJbHBW;odpX6)7tbO{%0%X)I z>>D!bn>rZLvpW}XSyk}895_3sZCW0)67ttM#_;Zh!78-D6dnoa2e^lCp6fx7_;Bik z>|C+x1sNuSU4E+H=j0t~c6-n*1m=hIE-um=c1>Rd=&BZd!%_!xgcTAabnB!Mvy=OV zI($4;v|9FUl$V6zvxfadz#dl0EwZAJ-ke4HPAnchZ0x_a79iQus=X3>Lc1xI`lcLh zJMKCV#GoZ5@N?;gm1~`}H!%oxpUl`8tcB2R_qqC8ovZ5u7DF<5U9!NLSR7yun>J;+ zo1!uaJD`F!<3jrfZP0G1>f{l+UOGJ_;5dR3^W)mdw({L;n^cN%|Na$#aiRbE{|agF zlzeGNRpFJ#1_%hYLP3_8gD(Muvv$wC01QwaD3l^8lu3{5j81PzDy=aEcZxm`X)tl! zB?8dm5oOiB=KLT|D!%^qsAB3PS+)*eJt^q#p7+uKH7yikU%D}GgVd%1H=L753^B&x zH4%JaHQJ*A^~RR=kcFK7aYx%l<@@fvvW>c}7u3uC)un`b;R!8bHYb*BT&7la(O?3Q zh`9rcxto-9+B*>8aYX_}8wz}#RKi^94CH7{2y|?nk##t#$bs1ZWL+UNHk-!bn-4hA zsa5JTsXfP6vuJ){?ep7O-b3tHGd#K47{y@aHL`PSPR+RWzCbdCmBU{@TBes?c-_to z4;Jx?inX|4RZ6p7O3~o_WqAQm%mNIeIW*n z(k;B!0S+G6NhS5MiVJ(=4$Ubq061r49c@<>vnbz^=#kWYx5mqdwcsl3SzYj3nMj?R z8@34T8>`+}`DmDp+3Z_*tGH}HMe9Na1KDP26S(JhDAx<#ui0ZmXA&#(^L{x&V6=J!hHMEVpDhWWAlSf7r+TYBEAM=LDIro+|wuF zcSV($aHnKm>!+EMNvTG)_EtPHtJY2tWUl=oCn4B(h29ZGwMv6vL;bZ!X9n2H{NVQI z<$Y&3a-Fs%s=3^|Mi!SF-~@slXk6*MD#X~|hMKdZQ}^R;s1g)zf09ngU1y=?4qwY) zy6y^_*WvR)2if6*kc|;ZxYnr@U?3NW#VN%CE-F6bPLn$UvS8g4oUfGPh%#y6eG)e@iWn{JnUz zjTrm?)ZVLI0eTgr0izS5xX;wZr1ama6rVd`P*@#)Ej!Slp0P_?&tie+Qwpkauh};& zyzF;wv$$&58aQ(XHjV?dJtJS5mwZ z7+du>byft%5eeu`vD?RGYSq-i#;r2gF=V?~0BmYTczhU`6qJ;%!*9eXpz&F5;^&d` zNvK$|D@JB`9k1W>T4W$C#K1G_?35j8QyCC0Ihe}KJL)hjh%o`@N9VKA1S}HuGyPD@ z{f&X{!48(aRd+(MsyG$`fZR;O@gVj~$lRa@?(C<7H&pg-HrbD?#$XB4;}kQn1FCu~ zo(Z+0Yi*K#$zz8s_2p2ZEbfa3-_5q|&1Gvjjw*OnPlY@MtesJHTaoKu zI)<<{(u-(-l!yB@xZeISr?Wb@E$&Lw1l-05#oD#6k;UefuWfmPCZQUeS}+@(Tj$Hx zlKH@y*oBOGvdr}PgxkwM)myy#>tbzBE-mr`)Kh|vN0964SI1u*>;EgGl@0Q1QRda) z5Ow`9qSoTm#s*T3E!by~zAXh6Ja%aD8uopdmt^y0JX-7vbnH@^+G;D_|D@C;evE(Y ziloa}T0Zy_VHj#hougixRH8%u!fdcWg{b?z97$@4A1j~8O6;qG6dfL@DF0c0NUXuk z5$>zb2y~=IPV_G5_p&B=32R;|1C7_dw|XH{6BY$t5_?^3(eUQdTl@37g(LLjzm#o2 zsF3X1^3b12#x<`Q_e~~s(=lXb^6-Tkcuf^vP&BWHd}o-Qau4ifl;9A}Ed`$+h&X>) zJ?cco$PHic0P6(R=B`8pn)+gj`MXli13uXV*6%FuKzD;>92{x}4&X%o&rribO$2&Q)Hbhz#p*REZMLTEzeLeTQ$jgAYANyp= zm~ilH!CxzSG+te7u6;r+g&1|;PKM(mfpA%IKPjKD#?8p8!*72V2gL3cw~THiL<#CR zMd*}N*Scu32JB$;Jeaw7@0NO;fRYBPwZgrorLtFZ^#({fn(e6RbV#097@Wp}r^}GlHwA6#b-El#37+|H>rAw6Qf}f$_iO9YNz8 zD5SCzUp3CS{ZT1iogq_T5g$0zmXaKO1KRNo?69d|y_;A2JNBlI-0BdntzlX9LhVyI zP6-aC0E`Pz4@e7UqFWdT9Gr0TL&&SUEtS|U*GtoEN$aWSP~M4ro_(T@$^?Wr@V}9` z_PE)eX&^o!`d`zMvd^cq8{idnib}uoGWpbFeQK-g5*4SnIy*Bh7aj! z1Nm`|NF1u`5*3F=5Bdcgx18^h81{A`XESG56x{&*RlZh1DYdSg;$>Mo1~$TelEF1> zHRH8f8@pWefly4TzNlZWj5ofBPiVgERTksI5#P9tS$^?;32LixUo;*n5hXCeA68#@PJv#!&OxtD!PN$s~?dX4t zY#JQ;c@AtAcx<0BNrg?|dpdbubMKonG3NnQk@dsw(sKh@_?6(g%(|XA|8fI?asDSq z%jxpGp8T9T{u*yJ8}hGhsHmq|`iMT8rB2O-0Zw0Z-#A^1sH~C>dhz74-T6zT-Bl*rN#FdYQLXGU0o6R9zGE4SL4-T4Ov@0L}Dr$7wQ%a z0I5@jFK`bIa)}O{{IiUcR|C3`^%HFy6K1_FzZsZL`n>Q4H}V3jOFm%<=k7VUMQ(SymD{Y&-SUHJ z8mNh9L=CS(eNfH4FlvvOv4Jt=>ES&wiWq4?yw9V+*lZ+wPSLl00vM_u7=*-Bu)4YB z`E(_QG=ii~V7U&Oqe(cdx`A(`__O)Lb(P`MQ=|y@9sl-obVAujN}fQ**$~H;^VDxO zG5QMO6fjhCaim4^vl|`0BGo^9<4>@9Xno8b%Kavi4wHOJ(OXG`s#r)}t{Q*&j zQPJWmT`6R%9*Z+8a!sku;rZ<*@%vyeK~(z#x@O|4fv`11#4(@(3GAk~1BqJ^u(ErI-amU)el0kzN?UezNqV=_B*)LN6 z?I2Hm)1y~oI8R8ZlEfDNL3aFN(#5Yxw>Gh?+XWfNOqQJItWZs%<9_a6%p-;q(E^;K zayVT67#op641P-}7DQLEq3Mal4#b}3JyO=XFHwLxm z@YH=OF&OUjzy)P@mTa-i?7U6shjXf1{P^wuCevtb(UnLvjErfR$H(4PzIO6)adg&8 z>%5-SD!Vc5=t3wVmrd@`nt1!(F2|LXzUB6Hih}YDsMr$M_&tB&YB#@I^^-`sCdPAE&RzWw9V% zt1rSjpHJOM9L6nLpDdjp`p2Z#`kLc_1M{gDOqk3mKQT$!cj<*$)L#Zq(Rdr1Q%0yO zFZ?!|j4;ZYw1r{+8|RV-6>jZS2*Eb_Xj4elQuF()a+Nd0mxKJX!6lx3GBI>C{j|0u z14t{dZrq1n;e(Ic?b4GMh&CC>j62runQoZ=Ao!;egRnhA)WGV3!T$q zg9P+O3FGgJzc9Xx87J^}sRsRMY{ngZw-cS1EHY`dm5EFdLcFZY$Aab`Nz^$iafaYZ zlRT_s`vNp!l}KMZMXrNe86%OwBo+mr3fM9ehbq)S1v}X*L}9tz&yNGI*{0|j(Ba~L zZP19O^wFeiP!XC?}wZ;_CE`K_aFy$^Z-Q#Qe_B81ABR|1$7i8uoY0?Wcn z`GW?y;`*7fzOI^VV%%l}+=Hx!!AWRV%LW@a zCLesC3+JlpsS5T{x=Nh{D4Zb_%~L~LZq4X-H#6ay+@^&^4C)Y;!kZ>_hb2?dR18u# zKaBy-*SI8i#~DArE}Q;xFkl5M`WDv-vWHS;=z>8(o%Z~a31W19-hI%8zdk62`s`in zZvC^WSy+fgVh|}+AuaeeTL^kE-(vGHXs?oBk$WH#>Q0Zwh9_4be&BCHh*Q-cO*mTv zmun*6Yh|dj!Zh@xi;WEs@aWM0HRHyzjZ4VP@=Dm{Yuq2}G{7G6E}q+8=o-Ab@3ZP>6scRgGFV34ul0XEavl9Mh%p2?DS?+&q@z_a`4w6z zMGHbsPaetooYh=wuO#E!KF1E5;)9@o`e+ZLGDbc-vgkhxB|sw9(AbMcZ!Y)|kpt5> zUjL~rfn*|l-v$wpGekI>03VNvC3s_HaLeP)8nB!AZL#m6_6=q*1lpB*n4)}^z zzDLvj9m8wWLbaCXosz zI(^vhkeW+^X5vA!JMqRUnzf(vS+Wcjo-Ya_1C>Vd^>D+?X6HF~U>iZfl;oeo?9Z|1 zU_5Ed#7cV*;hK#oG2ft-X*OnpVD>_DOB5K5vy(w&jtPAFLdx&>9ng)(G-5|b_!NWd zA;MKo4TFL5xAA99CQ>ff9p;LiS3i!dsiCDWRZ23xk@SpR@!tk3md-D))W~A+Lk{D< z>75NvBh|>f{I(=@`Q~KH2*8|X^WpGC=O)u#Yv&ungZ=fG6PVZW%A#_0lebRW$e0jA zw?6=U3+wHY@qZJ)podvrDlW>GWSof?EXQw7y-8KKiB*S#STs@lDdf;C&nPnxQT6YH z+JVP= zQbG83r%bTP=r)AHu!`uggji8E^%wx$y|SCck`ix8m#l>{ky~Zw-kls+w^G5lv5=JJ zJ+f7Ms5`pUum9tsBeBbEXt0%S@WyYkd3in)@T_jbn~@NQ?S?LVIow`U8(}1}tE^VfOkvFX6>rS-SpdX&dCXBN6#U%Z9DUyQC^ zw{k#BIiZ5T*ho9Qs1{jVApDkV(DETXSoN-LbeVdIfqjHqn+|b)Fd7h3`+Z2H=)KR? zA0wl)k-j_L_gSX=d>avv-7bE@`&ZSa^g4&a;5^01DcMMJx)5ZkUQ+Q=%SAv?Rdus6Ixb&aie0?;#&>$UxW+~c4 zZLK2}CGR?#)UEkn_B3HOlw52!a2pBBL5X`9kOa{t>3JY9?cU*%BaagGw@y$OC z`U|B+BXa&GqKEr#yUkxX5x{e z1xN~59pS_IvCC}H9x=~6H-Ds`XpRo(W#6`91(b8)qPz;$1||f`VlPflv=dvqNR|rL zt)+VC2}3`sN;j=luSCr@J}o_)QuQ7Dd6fRw>$-$nqAmd{!VIWD>0~V#6;>eEH5R8D zzDl%KKQ$egouMGImUPdiUJ3M;LgK;X4w8HjpN=x4H3VbSX+uY847&%MaO+XHEtr9A zO8=*@uMCKzX~M+a6Wl$pXprC@G$iPd z_H=d4l=RcpT{XFknJyg9+Zlk(T}oE==oM^^?`WIyBZ;6*b*Rl@97N=IM8BwKMle=10bX)ZbgHu|!*>oM^6q8Bn4jZL=UjUmwmC$`(S zsCIkZeHm{;{k%fl7@pN^D=v4zfLhEN(5(;pEstcwsSmj`lZ4;biEl`$ufAG}K7 zx|Txg-`xshJE9@{H{ zCO9)4ds?hAml3OAqKhF6BV_Q0Hs99b)>=vW6{zOTqur$JS^V&3_DkOx^`i6a)>t-4 zx1BLDCM#@HD50FJ*BM==h~Uj<(+Fiq&{YH$yN^Iwz8!|KGvR4((4dUxxLq^7##DKt zM3DV9cqFXe>FJQ*L@zwQz~6IXQt+b(=me}0dL=$VuLx+@r-zln zVbCINPjT#H5>yUrmA&4e^)>t5d3R_-N&jr)%?VRX; zi58pb<9~vSJbbx|{^i7}JtqXgnb0WEIp9Frh9`X$Bqc(#=*Bq=C0e_VDt=+BL1*6q zO-X^K_`v&N0_mXZyX-%YcTvh+nL%LBj{#Cpap~G3*4_rD!5K4 zR)83Ja9E*)B;n{>R)rf;z%UX&pA=^D)3?I8WOfKVBfMrwj^1LJgSw;XWc1*X%_0Wk z1~GyKDvmo-$K>D*YI;)*g?zYnCRg=4jc9ZxIO;-^7z~Gf1w$kK!wb7}o5S{B-HU+q zWN+o3h^V}}6Err0BYd#-!Z3Lo9NdExmN6~(FmSj>k_-ci@*Ipj#^LIU47Th>)JFPg z3n2`(pX5Eso9boF`EcDXkj&37r*|?2j51rOx{?=8ij)p*OkRKaV`Ib8NTD3|VNYa$ z_6j+=G$PyENdnt_aP}p-d!1C3q}E)fr4#`GdXuHt!%b7o9DBpZOPvBSgLo-M6*@^) z6(X{^Tr*>C(p7A!7A$t2S1t4HKMNXUgd0Uv`Bpo4X@BcKpPK9JoWgmdLE&)x!(jNM z0y7z;8-rvO!*mtX%fUIv(?4A`tV?!h%EWRYmRD2fAG02;tQO-#@drh$VCL1`MZo)X zSTq{h1_kh|`C6)MOMK54BV@Unyp#aV8}<}!+(lLE8Z34rW@goE5&vC?)gn3s{B?IQ zoWI=!qMckkvOJ>E8M&no3mfbmt?=eAjNV}D#UAY)^`4b51O1|_h-$6lZ>$Ns#K`<8 zEfNTTXZjK!9vc0FKvZkm_d;=>QrF^TaTD zMEG2N4V$ECwB%S`QA#3*HhhCslAx=eIv~-#mbnPAd>1LY2BAFfjH{;%rZ_f-Y_0A= z3Lk)XgbC*7Yow4aAC!3SGW&xB#*H>ohY;*mH&{PHFQ~2eC)wM0jctVNZVticqz~;->23fR+32% zz3tZTYx$(dRBR~@(l>TI{jmKtXTzpUPp`AFrGY6N4;#9#!-zZ>G;Fcc<0AV8BRP&TKZcn1j>oF9~D zD~E1PKkyKcf(fr&zlV`=S)v;VROOnDD%6}GTn2$uXN*3l7@2)^Y>)++RnJ18f9g_H z;PH^~;NvR(27mrsP}8|@e^M!4L5CzyWU4>C4B+&gkm~{XWrcCTDLDtESOgnF_sQw` zJHly3Cioh^cfLQN1o6@KVrxSQ*?KWTtaE?O$lAgw9V~j_L}$I&9j<<7ygQmg%jbwQ&oLY3QXF%F+ThuXl32A>c-FhrP}+#0u-XX$u_ zd@6WnRd&0b+n;jZSDwG5m`9AxM9;`Boq!t@k~|Vmxtbo`KKtiX>HQ%ogYXZq_eI=( zBm)7EyjQT&w!Pl$I2-2otETHp9M;T(Qz+f{wKEAZP3-~rh&T(h(p5kVE z$ygWBm<(y=SDSV!@9v@mm1}E<`@Q{Ty2<1HO8o0ojM$5Zp^$N@JsCxJKW+M)SVI}a zN-%(E6d)Q_8rb`~9T;AZbCMx$;AcA$DRax(=Iu;!rJTpyC6Uli!+lmZK7nx-d)s{@ zvHhp-mF{HCJ>J49#A=E%&Lpv=;W}*i>N7kF^Q@P z3=K}#fvteZ>0FP5+ra^%D;-gsgH9>*-R9OlRO@0ffDjHMf*d~5VFqP9^FE?hTCRH5 zE445|)0N1MDIkAGAEyQHQfi11DDM>JK;SKj2$4vBK%LZZ)%7jvI5;-ga%QGlcYb_T z%<$RvuTxpM7`#>5+UxY^6l9ud3k9AkgUiRyS05W~-*P#(luoacq{}V| zy2xXl`S5Li)}`+KLJUoYySyVu1|0nW0+|wjL?81}19t*sAuulD?7(r;{rB>sLwnic zxpLHcFq0vs>;2v``GKc`JO0sMQ`p<0&pj83^D&BDzu+!6$Jf(om{Nn2Yyaq?$pkko zz-n8auv8~H&UHpln_oXFh!suB!b!%3Af%AB!36HSvG5Ssw&DBpiYWbdG{yYxhL&oF zUf-u-TU1xubZtssRFS`E94>y$oBT|RjcP+Qd&p7eqCXHwNf=yaF!_Z%vSgiyFzQR< z&ZPwO2^OoiCwjYmT==jWoWCFfkLS667sYUmtIuG+f5?cdY1P3!?4BN)6NtAbr(Ruq zaCiC6Vys@6Q{!GxOEJzAYTe_Edng?%g716zWRjRjawJ5_iTfqd;xO9(^3!#E>YFM< zlMAI>Qk|Ok9=if4u+Sox5O}Mk69cE(O_=?>wJz>ny+rt*LCMVD+UA-!C{t@sZ?7W% z=?oQC!K&6du0Hsn3=_F>n|NXw`4GNlracskzM-;~(jK+1e)IFQ^>$C#x$mw@xwdO* zXt=%DMVvnvC`id9KxN`kykTEE8yI>qtMLxX3mygwYwO(-nI)D+X0KdGa%Dd@5;tJP zu=`PY-PoNx$x)@XMxE)c#AfO=Z}eOsBsTCQ+0rkAdy;}1AV7LnbEfc8Mdxx(n;1a-6#T9Guc z_ru(9u6PTx?}b>q1PM}r{gKdCv#a%#0d;DJ4$7?9(q9)863{95Ex7u{iA=TB`b9}8 zwtFXQKbJWJh0SA6$4^W2QuLL&MQbzCQ9fT%JsWzm;sWo(f#Cpk@#qP#G~&UZ&zT&c z!M>BwU+iW4fnA9L9m8nAc&bRw{miVtxqNyzvYD|N`;D@{Dg3WH3;#gC6Nby4p}2=O zqhy;^%;5*qj$UaP-1yN@BKottP4ZZH0a`8Xm(WDE(TDA+7i*D>%Xp$6C4Ktj73&UC z)^DkFnLlNlrz2m(C_0~0lAo!UVrN^y8xD;Ym)BZl{u!v(cjVu0Z81=G@X!!Gs^gij z4*2P<-Y(jQWWRzh+kz0^<*(L@KL!2qTSD;>La<~5?aG(Ihr<~tOUfi390JX1^sAM_ z;_72g!F5em0V(Go|DM=a!JE^qTguG-g&uA9U&15ak;J9#gL1qs{S>Qvy_ zB0R)WFC_B*I#q8n59|ys`zPZABbVqj)Xw|vO0#_&?+=<0LS`w!IKgniDH0G*q1qrSGoBcf)bPrm?DqR**g$Im2ofu5G~|WO z=24O!Ehu|1X3azC_#Z9|g$0#us`*k49~GqH(i4u;aQJqUQ$-s(H??;k^RJgRkO^Hv zQDwN<|Guy(NE7=Ov)=lbHVmy@tWB{7tjONS>9#S|Kkf!&Vi9-9M+xx|KtEKYbjm}l z1O}$Ok9NW#luk2&+oUDB_(TMRNP;$A?ax*Mx7to0Uj+A7eYRTN36SoHSQ7G9>1<15 zY-bg17kM0KSd};3g?{&a{Tr_CR&>3fY_p(TUNEyAYx$2j|Tbw->B>DJNh_B0W5-P^(jf8lR?JF1)b<71}R#O?C- zt&UkQcHv-29j@1?!bO71_3t5qy%)Pn_%SpYB_CtnhB@X2(~qUoccw`m=nHyv^eTd6 zhq9p`_F-t%x%dbpQJD`c(OlFZH}>4Pjh~_vWMFc5a7+CmN5f^y+A`;)2n0pFasWfkAx%C- z4KE?yyzh|ygiP(lq@KD|j!f1U21`gKIi$5v4)-`|CO0N7D2ZC`>yl%!tHH%?uVSdI zd#GmBZ>4T5S@Pk)r=QjQe|~gE@jbtGF$k^RSeif#v{pkUTgZ!(Tp%OuwHTbl^X*kj z$?m>gtadBwH@s4U<7Xo8)5OnBi&BxDzYcu#+Q-nq1yH{Ew3AA+>(2S0jOkqb2z`29 zFlPZ3f*_VTHImx8y7pxB6+wYGE)n|#)fIneV~0pFk%htc*N6G!xweu93}723bD+;Z zoGon+on~lqkk7zWjn&}eHA=aa6n#)AEX;Qe4xg|)AE@Gk=1uKCtjx85=*8YLgKE#c zA&+K93XOL}E9Eb1i4yTl@7qhBdY&}<7Q5C6Vmoi^w7)I4pJ`b20tJiyKJ@!}s#+`H zGScC4kbGGbfopRfOJ*+ly(jvH(O#@#1>E+Z%(A5Du`Kf{+}8w;sdXDtI6VXrha{;G zUY}u$=z)?8j2lGoZb~gWJ+z277z-Fklws7N$!BJO${U1~cj7KF<1u}^HDlFJi~b`CZGg~drn<(-3;X1ZWD2JSK~e=p zK}V_GwIv4ILW+vWYBs^tV*O@j zHNUNbCLcnoEaZUQ7*z4C_Iv->;nk@a7u-tM@}@a93x@L^`0dEGuruNFVft=7;Txsf z;Fut6qdBZ{UYxZPUJuvrpQ_i+#isQ>Z4J|?6`l{QnI6}6ujdoT)C0KWRgkPN8aSVPwZfFZTZkQ0HM6rP=$g$`#bE!~6kgzaG)VOGJJ+tq20lP`JpuYqU8T{RWRVAjTpichiM7E>(@>Nsy- zK9PuEsKQ^~>b?E|_ELCl@V{={inBdsc7;e=*Effi`!Sm#g)J|0GshI=S9_l<8f5z( zR(bU`yFGl)VFf9Z@UM}EU2L78rU_p8s`*ovH26CnSfEYQJb~&^Ee*_$_x&1uZ4c2~ zIa{P-?&ZK_WVB0M8%0UDUSdH`01n(+oy=QsjmcB>B6SD8g28=C(EG&!b`(4$A*An! zdJ-i65JTGR#9&~gbMh;1- zI8XG(CgO5S&Des%{(^oeGf^f{G7`g$CI$WcKlftF{BDl>_S_BK0a%=H#MZ}qMRo?bh%V43>9>+R~k8tXy@MbFTYNlE^SJh(Xj!XM#i_?zK zzW7eBGo0uv1*GcYJ(Jjp=lz|bt#_|i+QvM#2y9R}EcX*h`3}}0-v>g9S4kUj9luB}E!8sgXP z#(H$9?p*M>u1vk(F>fVb#dEplSFz#mgL~X=9@D1|K8+*Rey*LV3p4+SMw|9YxhhCl za9Omcl)W9a>v$?)Zj4bcN7s({`9O4@AU9zGO-KB8q!8D!%kB!AVISCRrXubs7nCwFr0=BCg4$;YY(>E|vI#=rh79=^pSLJ$Hn&#T6P z<6gd7K)l}SM66Dr3fbL|&nGzP?*5i=;CDRlkER(x=bf|C+NGo#hO z=mYl-vl-u@;z{NUoc(*Cr~@b+*~@L4OkP&#zJC=%oEOhIQyZAvZtfoxB-oKIOpOY%)pv#Z z@iv03_(5dg%AgdT?M?!Wc*JZzJk=M+U>GZL#~ z;M_3rGn6>R#gea1d`b$OJ)8_V!noX3nEpEbi2L_!1t(>Ub=~tWDFiRS1?IWLH zv$IM9swq2GdBT)`It1b|N~od%HsFEoL4-TmfV11#$qe`sqIU(E(87kD3!%`IBMM*_ zPb+zaLm*)#lu$X8aJC1dx`u%8SK2M1Dn78WF|WMRK?%T?$e)PQTtWnRs=HjOYhRq^_6m8TD1#boefuF-#R!YaWSi3^ZDZuyC^Z5W266D82Tq0Tx1MXMXMbyh3DZC+q0I=*&;fYlHQcI#P18 z6XC351f8!nI1tsX7wIIasxcTE@)riqX=0M?jPQ{&@x-jVPHHBO*MdI=o7RTnNLshQ zj-*`=S&WJihztED=Zm}RFHc16`CNnC{vZJ^j+1o|0i(T&IsCW1s+oQZbBX7{V}z_H z;xap$RZf{qD_@G34zRJxyLAO0cP8~kZ&saZM*V?IDD^}?HMo`!3W#ll&zHo<#Z9sm zbX8`X%m*+fSZr_h=7b^?i=c%)ATnV2&_X|^oC^d%ZZ~sf)m{l9s(`Roa1?XSoKdek z*UrB@O{RYH#ycC~wnErM(N*I@WcQdr)SF<<89}EV@@o0Wk&ECU~HjIrW&&K=z zIJ?HFTtN&Yx~mBUkcl#`Y*$%;Yq!4t(0ls8eVx1&-Wm@6wk6RN990?sRLkMW))A_r zh>y)SU-&@W8$k#1XzX_6A(FBGJq25mfU#=NuC&0y)M`eb|5bswzY2yQ3;~&_tM;(I zLYVMLPr?b*$M`cblAOKs*vjLti8^1v+G%9NCd=QB3dkBj|m7i%6D<)uxbpr*- z9y~Uww|*!k%6x6L9=NtsUxdWKS@l!l)2OU=Qp=D1S=rWb?7@WvdSP{}QssxdFN8ocskrFb8 z5}py`?)m$e~b`3|Ixrr#_))}>JXVko21BJ?$K5QLn;}>pwZGSTQcw`DsXa- z%U__hzVKi0LEq_;5HM@`fDqp9Rt|U8livWLI|v`So$z~GLm*sk5Z0nj^wAGH!^#p2?e;?cDkOLCsA4RRz`)hH#KcI+P{>|-EP~Z+Qn6V??(rIk$T@k zN|JQSlrGkjiiLG=kB&qB8#DStYEj}5Y<;Ko1AMtMdDO{OAppzk6Ej>kO19qA{*4?R8?5d>b9JiZRZLrpe(-!g)v`5pQfvtroVUUZQ@a@2K9`5=PdC%Evfa4BrGx(2+{ zr%wjd|9w^`etZA8$P#$AD%)H<;%4CbnxneQ@ytTIcNTSg(t`g64_56x>bt=fY@Zlv zel)--OCQNfD^;|{tbpRAM&HFYij*!g~XPqGR9_xh2_ z+n*8???C3R_lqJ!)`$HadLNA#VUKBqjRG zGD#%DN2#zcJks>K0BkG1?snfu7M~R&ij~)I2+PzJyi$MT6#Fu8L@L)k;ER#Jd`Lr) z!ZaV+^b*1iwhGn!n~Se869E)s{LYd4pSu96p;YywWy7T)?<&?Ce*Sv+rSdd=2VDPk zBlQz%UML`XLO{S56z4GRFGqPt^{)V0?El5haLg$cE5eh&PNu!bN?i+-_c`MOUjrDU zl8_VxqI+PiVGFL+<>t+c2+CLq&T`RyZiCX^_Ia^usbci0t33!5_>)q1ux+piV^*Fa zSYR<{mNoGO28`CC^N%+69LhqRHQ$gFdnizrC}YeP=CUr-Yc_LTS20nq&j5RqRq9U!!r^Vti?}w z+;UABRk%Pq)ZZ^^$zEMQ$txo*rf-f{F!)KoX<5{ra^_7xzLQ7g*@ycuR4XYc(4_LU z=!LVPpta=zOvwpfdQJY1kAKo$h)-Ds#e>&nW6{2{<6OQmd-we4-6`DKYf@d;zfbQB z?iac#?q@mF)1nnx@T2`T*&h*gI;T5rp=Nr5*GO8u295aky37gxX8k3|HNM^z#$Ouv zB_7U}N|xWQJa_51uG&S}VDe*t=ZS(4h!-jU<>H);qE%( zpx-GBg1v8Tmd07m7{!e8pjhc2sZ-w;M!}dXdT3M~Ftt3QW1nljHB;ofxgRyx)8hP= zsft83=V*R-c>dbt&wFJnGzFmV;NA~(YY4785hXg)#0EGL`JUxb8B{W{tge)Xp!{yz?^ahY2okxrwHWCm z{t@$x;jeG2+MM~xo zk}|bcPhNp9ng<4xfqtvrHtET}uDSs`izaqXce`{#WFQZLo+|$vME5%9<*RgKbJX=U zXGv=yQX(};_4V(+a52%kx3OSsYW1o1XVHmdhPqE0rF)l1?9P+JaOO|>o{&b~ng%i* zJ=^Xxt#Nj@l`=N+jZb8CudtOi0FBqjSA@Z5&!GQ3Q|yn3%d-5w*FI@oBN|3_Ko;_+ z{Tj~N($JDm*2l{}1@ZCBZI@seQt~B3OSHio)$WLh5|es9u%ghva278;5LJw(z1fw> zqVO%>d|Kuvq^XbEZic?{tyi;IW^%vtjX659i$A-XnC#ASZn(P7e4^9;@qS%uvFEzj zxK&d}5%i6J01Za5NQ@tUk9uYJ-`umG;{KYP@pv}5Y{x+Qav7CnEDVtby{^Lk*U47z zJmzX4rdo4Tb`JaK84R~tD5tB0s8nu_?pJaI76zp#xkxFcrO~uq|K9vyx8b{N0@-zaKBM@zuE- zU4lhT+{1jS>(AwT#b>@SfhYr*$^RrvL+QLuIrK;Ij%n{=q$SEVDnbPW#AHw4PWIki zRCrWd+xl?X9Y`ANsHbQ7pRRzv6|GlJmYd%CT5L{R?j6d=-(brGPrfJ2-B~dGN@1Y* zdbMe7<@nEA3DrSLJk`H+ zXpeSQUwWqE0?#?}o}#DXKzmHUE7EY}dcpe4YtE3`f5Q6w@a+ulv6IHwkdrpG3Ddw6 zi^WStu`Eh3{IAa&W~4ze#;L#>CN_HdNhQwCj$J{n>i8FP1znbNL9z#-7Ks)4D_j&H zr|6deX15`_ zu finden. Die Plugins sind Kategorien unterteilt, die von der Interaktion mit bzw. Ansteuerung von externen Geräten oder Diensten ableiten. @@ -44,6 +44,7 @@ Die Plugins und die jeweiligen Beschreibungen sind auf den folgenden Seiten aufg /plugins_doc/plugins_protocol /plugins_doc/plugins_web /plugins_doc/plugins_unclassified + /plugins_doc/plugins_all Wenn ein Plugin Autor die Informationen auf diesen Seiten aktualisieren möchte, muss er nur diff --git a/doc/user/source/plugins_doc/plugins_system_header.rst b/doc/user/source/plugins_doc/plugins_system_header.rst index 45344e4a3b..67e6a79c30 100644 --- a/doc/user/source/plugins_doc/plugins_system_header.rst +++ b/doc/user/source/plugins_doc/plugins_system_header.rst @@ -21,8 +21,6 @@ die zentralen Funktionen von SmartHomeNG. Diese Plugins sind zum Teil auch enger von SmartHomeNG gekoppelt und müssen daher in der Version genauer zum Core passen als andere Plugins. -Besonders das **backend** Plugin, das **cli** Plugin und das **visu_websocket** Plugin müssen -genau zur Core Version von SmartHomeNG passen. |br| |br| diff --git a/doc/user/source/release/1_9_x.rst b/doc/user/source/release/1_9_x.rst index a73c8fb744..986969f38e 100644 --- a/doc/user/source/release/1_9_x.rst +++ b/doc/user/source/release/1_9_x.rst @@ -1,10 +1,10 @@ ============================ -Release 1.9.x - tt. Feb 2022 +Release 1.9.1 - 13. Feb 2022 ============================ Es gibt eine Menge neuer Features im Core von SmartHomeNG und den Plugins. -.. note:: +.. note :: Diese Release Notes sind ein Arbeitsstand. @@ -189,7 +189,7 @@ Plugin Updates * Improved error handling * Updated WebIF * Updated documentation - * Bumped version to 1.6.0 + * Bumped version to 1.6.1 * casambi: Resend commands on transmission error (e.g. due to sigpipe error) once @@ -380,5 +380,7 @@ Documentation * Added "updated_by()" function to reference page * Consolidated logic documentation to reference section +* Added Plugins/All plugins page to navigation + diff --git a/tools/plugin_metadata_checker.py b/tools/plugin_metadata_checker.py index 0481ce7921..5fa77223c2 100644 --- a/tools/plugin_metadata_checker.py +++ b/tools/plugin_metadata_checker.py @@ -603,7 +603,7 @@ def check_metadata(plg, with_description, check_quiet=False, only_inc=False, lis # Checking plugin name maxlen = 12 if len(plg) > maxlen: - disp_warning(f"A plugin name should not be longer than {maxlen} characters. {plg[0:12]}", f"The plugin name '{plg}' is {len(plg)} characters long") + disp_warning(f"A plugin name should not be longer than {maxlen} characters.", f"The plugin name '{plg}' is {len(plg)} characters long") if plg != plg.lower(): disp_error(f"Invalid plugin name '{plg}'.", f"Plugin names have to be lower case. Use '{plg.lower()}' instead") From 54feefc422201c003eba80157f1d1115a2e15958 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 19:43:30 +0100 Subject: [PATCH 93/95] Documentation updates --- .../source/plugins_doc/plugins_all_header.rst | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 doc/user/source/plugins_doc/plugins_all_header.rst diff --git a/doc/user/source/plugins_doc/plugins_all_header.rst b/doc/user/source/plugins_doc/plugins_all_header.rst new file mode 100644 index 0000000000..4ab4e2d745 --- /dev/null +++ b/doc/user/source/plugins_doc/plugins_all_header.rst @@ -0,0 +1,25 @@ + +============ +Alle Plugins +============ + +.. image:: /_static/img/all.png + :width: 200px + :height: 200px + :scale: 50 % + :alt: all plugins + :align: left + +.. |br| raw:: html + +
      + +Dieser Abschnitt enthält eine Liste aller Plugins, unabhängig vom jewiligen Plugin-Typ. + + +|br| +|br| +|br| +|br| +|br| +|br| From d18ad60141277b86cd2b3a6a616d69fa95b92b37 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 20:09:07 +0100 Subject: [PATCH 94/95] Documentation updates --- doc/build_plugin_rst_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build_plugin_rst_files.py b/doc/build_plugin_rst_files.py index 37ace6267d..f58c6271f4 100644 --- a/doc/build_plugin_rst_files.py +++ b/doc/build_plugin_rst_files.py @@ -424,7 +424,7 @@ def write_rstfile(plgtype='all', plgtype_print='', heading=''): plg['sup'] = "`"+plg['name']+" support <"+plg['sup']+">`_" fh.write(' | {plg:<65.65} | {vers:<8.8} | - {desc:<163.163} | {maint:<15.15} | {test:<15.15} |\n'.format(plg='', vers='', desc=plg['sup'], maint='', test='')) else: - fh.write(f" | {'':<65.65} | {'':<8.8} | {'-':<165.165} | {'':<15.15} | {'':<15.15} |\n") + fh.write(f" | {'':<65.65} | {'':<8.8} | {'.':<165.165} | {'':<15.15} | {'':<15.15} |\n") leerzeileausgegeben = False if plgtype == 'all': From 57a9ae434fe33d81783ec0836d47e3882678b4d0 Mon Sep 17 00:00:00 2001 From: msinn Date: Sun, 13 Feb 2022 20:20:25 +0100 Subject: [PATCH 95/95] Set repo version to 1.9.1-master --- bin/shngversion.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/shngversion.py b/bin/shngversion.py index b28b3c2be1..8fcf38bb77 100644 --- a/bin/shngversion.py +++ b/bin/shngversion.py @@ -77,8 +77,10 @@ # Update auf 1.9.0 wg. Release # Update auf 1.9a wg. Kennzeichnung des Stands als "nach dem v1.9.0 Release" -shNG_version = '1.9a' -shNG_branch = 'develop' +# Update auf 1.9.1 wg. Release + +shNG_version = '1.9.1' +shNG_branch = 'master' # --------------------------------------------------------------------------------- FileBASE = None
    • {{ _('Item') }}{{ _('Wert') }}{{ _('Wert') }}
      {{ item._path }}{{ item() }}{{ item._path }}{{ item() }}