From e8385cd835c3ae6029ae0acc09a7220c397bb3de Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 9 Jul 2023 00:23:19 +0200 Subject: [PATCH 01/16] executor plugin: fix major issue with autocomplete. Now full item paths are available instead of (non existing) item name references --- executor/webif/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/executor/webif/__init__.py b/executor/webif/__init__.py index a74caa780..1395763a1 100755 --- a/executor/webif/__init__.py +++ b/executor/webif/__init__.py @@ -48,7 +48,7 @@ import csv from jinja2 import Environment, FileSystemLoader -import sys +import sys class PrintCapture: """this class overwrites stdout and stderr temporarily to capture output""" @@ -197,7 +197,7 @@ def eval_statement(self, eline, path, reload=None): def exec_code(self, eline, reload=None): """ evaluate a whole python block in eline - + :return: result of the evaluation """ result = "" @@ -284,7 +284,7 @@ def delete_file(self, filename=''): @cherrypy.expose def get_filelist(self): """returns all filenames from the defined script path with suffix ``.py``""" - + if self.plugin.executor_scripts is not None: subdir = self.plugin.executor_scripts self.logger.debug(f"list files in {subdir}") @@ -296,7 +296,7 @@ def get_filelist(self): return files return '' - + @cherrypy.expose def get_autocomplete(self): _sh = self.plugin.get_sh() @@ -310,11 +310,11 @@ def get_autocomplete(self): if api is not None: for function in api: plugin_list.append("sh."+plugin_config_name + "." + function) - + myItems = _sh.return_items() itemList = [] for item in myItems: - itemList.append("sh."+str(item)+"()") + itemList.append("sh."+str(item.id())+"()") retValue = {'items':itemList,'plugins':plugin_list} - return (json.dumps(retValue)) \ No newline at end of file + return (json.dumps(retValue)) From 5e1fe2c4a90f7352108a4f830aa6757c04cc2eb1 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 9 Jul 2023 00:28:46 +0200 Subject: [PATCH 02/16] executor plugin: allow sorting of saved files, minor code improvements --- executor/webif/templates/index.html | 83 ++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/executor/webif/templates/index.html b/executor/webif/templates/index.html index d34350a03..49cadb331 100755 --- a/executor/webif/templates/index.html +++ b/executor/webif/templates/index.html @@ -3,12 +3,6 @@ {% set logo_frame = false %} {% block pluginscripts %} - -{% endblock pluginscripts %} - - -{% block content -%} - +{% endblock pluginscripts %} +{% block pluginstyles %} +{% endblock pluginstyles %} +{% block content -%}
@@ -358,12 +404,13 @@
{{ _('Instanz') }}: {{ p.get_instance_name( {% endif %}
{{ _('Plugin') }}     : {% if p.alive %}{{ _('Aktiv') }}{% else %}{{ _('Gestoppt') }}{% endif %}
-
+
+ + + +
@@ -381,13 +428,13 @@
{{ _('Plugin') }}     : {% if p.aliv
-
+
-
+
From 8195163529697103df6a7b181878188eec96368d Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 9 Jul 2023 00:37:30 +0200 Subject: [PATCH 03/16] executor plugin: re-write autocomplete method using worker and promise functions. Now the plugin can be used immediately without having to wait for fetching the autocomplete dict. --- executor/webif/templates/index.html | 110 +++++++++++++++++++--------- 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/executor/webif/templates/index.html b/executor/webif/templates/index.html index 49cadb331..8c9f66292 100755 --- a/executor/webif/templates/index.html +++ b/executor/webif/templates/index.html @@ -104,40 +104,83 @@ }; function get_Dict(cmPython) { - $.ajax({ - url: "get_autocomplete", - method: "GET", - dataType: "json", - success: function(myDict) { - console.log("got Autocomplete") - myAutocomplete = []; - for (i = 0; i < myDict.items.length; i++) { - if (myDict.items[i] != "." && myDict.items != "..") { - myAutocomplete.push({ - text: myDict.items[i], - displayText: myDict.items[i] + " | Item" - }); + return new window.Promise(function (resolve, reject) { + function successCallback(response) { + resolve(response); + } + + function errorCallback(response) { + reject(response); + } + + function fetch_autocomplete() { + CodeMirror.commands.autocomplete_items = function() { + }; + $.ajax({ + url: "get_autocomplete", + method: "GET", + async: true, + dataType: "json", + success: function(myDict) { + console.log("Initializing Autocomplete"); + let worker = new Worker( + `data:text/javascript, + function createAutocomplete(myDict){ + myAutocomplete = []; + for (i = 0; i < myDict.items.length; i++) { + if (myDict.items[i] != "." && myDict.items != "..") { + myAutocomplete.push({ + text: myDict.items[i], + displayText: myDict.items[i] + " | Item" + }); + } + } + for (i = 0; i < myDict.plugins.length; i++) { + if (myDict.plugins[i] != "." && myDict.plugins != "..") { + myAutocomplete.push({ + text: myDict.plugins[i], + displayText: myDict.plugins[i] + " | Plugin" + }); + } + } + return myAutocomplete; } - } - for (i = 0; i < myDict.plugins.length; i++) { - if (myDict.plugins[i] != "." && myDict.plugins != "..") { - myAutocomplete.push({ - text: myDict.plugins[i], - displayText: myDict.plugins[i] + " | Plugin" - }); - } - } - registerAutocompleteHelper('autocompleteHint', myAutocomplete); - console.log('Stored entries to Autocomplete dict') - CodeMirror.commands.autocomplete_items = function(cmPython) { - CodeMirror.showHint(cmPython, CodeMirror.hint.autocompleteHint); - } - }, - error: function(result) { - console.log("Error while receiving Autocomplete") + onmessage = function(event){ + let myDict = event.data; + let result = createAutocomplete(myDict); + postMessage(result); + }; + ` + ); + + worker.onmessage = function(event){ + myAutocomplete = event.data; + registerAutocompleteHelper('autocompleteHint', myAutocomplete); + console.log('Stored ' + myAutocomplete.length + ' entries to Autocomplete dict.'); + CodeMirror.commands.autocomplete_items = function(cmPython) { + CodeMirror.showHint(cmPython, CodeMirror.hint.autocompleteHint); + } + }; - } - }); + worker.postMessage(myDict); + + }, + error: function(result) { + console.log("Error while receiving Autocomplete") + + } + }).done(successCallback).fail(errorCallback); + } + fetch_autocomplete(); + }); +} + +function autocompleteSuccess(response) { + console.log("Filling autocomplete dict... This might take some time!"); +} + +function autocompleteError(error) { + console.warn(error); } @@ -260,7 +303,8 @@ }; }); - get_Dict(cmPython); + + get_Dict(cmPython).then(autocompleteSuccess).catch(autocompleteError); cmPython.refresh(); From 50a17eb249ef797a8a1a2acab4812c4d598f8d92 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 9 Jul 2023 00:38:26 +0200 Subject: [PATCH 04/16] executor plugin: fix autocomplete dict. Previously (sub)items called "id" were fetched wrongly --- executor/webif/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/webif/__init__.py b/executor/webif/__init__.py index 1395763a1..30b6b70c5 100755 --- a/executor/webif/__init__.py +++ b/executor/webif/__init__.py @@ -315,6 +315,6 @@ def get_autocomplete(self): myItems = _sh.return_items() itemList = [] for item in myItems: - itemList.append("sh."+str(item.id())+"()") + itemList.append("sh."+str(item.property.path)+"()") retValue = {'items':itemList,'plugins':plugin_list} return (json.dumps(retValue)) From 5078592efa7b230812ab1641048efc784c4ad652 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 9 Jul 2023 09:20:22 +0200 Subject: [PATCH 05/16] executor plugin: fix and extend user_doc --- executor/user_doc.rst | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/executor/user_doc.rst b/executor/user_doc.rst index 7c749a259..964d35c99 100755 --- a/executor/user_doc.rst +++ b/executor/user_doc.rst @@ -16,7 +16,7 @@ executor Einführung ~~~~~~~~~~ -Das executor plugin kann genutzt werden, um **Python Code** (z.B. für **Logiken**) und **eval Ausdrücke** zu testen. +Das executor Plugin kann genutzt werden, um **Python Code** (z.B. für **Logiken**) und **eval Ausdrücke** zu testen. .. important:: @@ -35,8 +35,8 @@ Damit wird dem Plugin eine relative Pfadangabe unterhalb *var* angegeben wo Skri Webinterface ============ -Im Webinterface findet sich eine Listbox mit den auf dem Rechner gespeicherten Skripten. -Um das Skript in den Editor zu laden entweder ein Skript in der Liste einfach anklicken und auf *aus Datei laden* klicken oder +Im Webinterface findet sich eine Listbox mit den auf dem Rechner gespeicherten Skripten. +Um das Skript in den Editor zu laden, entweder ein Skript in der Liste einfach anklicken und auf *aus Datei laden* klicken oder direkt in der Liste einen Doppelklick auf die gewünschte Datei ausführen. Der Dateiname wird entsprechend der gewählten Datei gesetzt. Mit Klick auf *aktuellen Code speichern* wird der Code im konfigurierten @@ -46,7 +46,7 @@ Mit einem Klick auf *Code ausführen!* oder der Kombination Ctrl+Return wird der Das kann gerade bei Datenbank Abfragen recht lange dauern. Es kann keine Rückmeldung von SmartHomeNG abgefragt werden wie weit der Code derzeit ist. Das Ergebnis wird unten angezeigt. Solange kein Ergebnis vorliegt, steht im Ergebniskasten **... processing ...** -Mit einem Klick auf Datei löschen wird versucht die unter Dateiname angezeigte Datei ohne Rückfrage zu löschen. +Mit einem Klick auf *Datei löschen* wird versucht, die unter Dateiname angezeigte Datei ohne Rückfrage zu löschen. Anschliessend wird die Liste der Skripte aktualisiert. Beispiel Python Code @@ -55,7 +55,9 @@ Beispiel Python Code Sowohl ``logger`` als auch ``print`` funktionieren für die Ausgabe von Ergebnissen. Die Idee ist, dass Logiken mehr oder weniger 1:1 kopiert und getestet werden können. + Loggertest +---------- .. code-block:: python @@ -66,6 +68,7 @@ Loggertest Datenserien für ein Item ausgeben +--------------------------------- Abfragen von Daten aus dem database plugin für ein spezifisches Item: @@ -111,4 +114,20 @@ würde in folgendem Ergebnis münden: ] } -Damit die Nutzung + +Zählen der Datensätze in der Datenbank +-------------------------------------- + +Das folgende Snippet zeigt alle Datenbank-Items an und zählt die Einträge in der Datenbank. Vorsicht: Dies kann sehr lange dauern, wenn Sie eine große Anzahl von Einträgen mit Datenbankattributen haben. + +.. code-block:: python + + from lib.item import Items + items = Items.get_instance() + myfiller = " " + allItems = items.return_items() + for myItem in allItems: + if not hasattr(myItem,'db'): + continue + mycount = myItem.db('countall', 0) + print (myItem.property.name + myfiller[0:len(myfiller)-len(myItem.property.name)]+ ' - Anzahl Datensätze :'+str(mycount)) From 1ba38c930a233a5d991ee140aa4057ed24a5beb3 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Sun, 9 Jul 2023 09:20:39 +0200 Subject: [PATCH 06/16] Executor Plugin: bump version --- executor/__init__.py | 3 +-- executor/plugin.yaml | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/executor/__init__.py b/executor/__init__.py index c4f70e5b8..4e02a5cf0 100755 --- a/executor/__init__.py +++ b/executor/__init__.py @@ -40,7 +40,7 @@ class Executor(SmartPlugin): the update functions for the items """ - PLUGIN_VERSION = '1.1.1' + PLUGIN_VERSION = '1.2.0' def __init__(self, sh): """ @@ -142,4 +142,3 @@ def init_webinterface(self): description='') return True - diff --git a/executor/plugin.yaml b/executor/plugin.yaml index 3933b87be..3f26ce297 100755 --- a/executor/plugin.yaml +++ b/executor/plugin.yaml @@ -6,13 +6,13 @@ plugin: de: 'Ausführen von Python Statements im Kontext von SmartHomeNG v1.5 und höher' en: 'Execute Python statements in the context of SmartHomeNG v1.5 and up' maintainer: bmxp - tester: nobody # Who tests this plugin? + tester: onkelandy state: ready # change to ready when done with development keywords: Python eval exec code test documentation: https://www.smarthomeng.de/user/plugins/executor/user_doc.html support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1425152-support-thread-plugin-executor - version: 1.1.1 # Plugin version + version: 1.2.0 # Plugin version sh_minversion: 1.9 # minimum shNG version to use this plugin #sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest) py_minversion: 3.8 # minimum Python version to use for this plugin, use f-strings for debug From f97d7929cae26e8e04c9a49cc95502ce069fbb6f Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 10 Jul 2023 01:13:32 +0200 Subject: [PATCH 07/16] executor plugin: introduce popper.js tooltips for sorting --- executor/webif/templates/index.html | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/executor/webif/templates/index.html b/executor/webif/templates/index.html index 8c9f66292..5a6b4a4d8 100755 --- a/executor/webif/templates/index.html +++ b/executor/webif/templates/index.html @@ -235,12 +235,17 @@ $(document).ready(function(){ - te_python = document.getElementById('pycodetext'); - te_resulttext = document.getElementById('resulttext'); - alpha_asc = document.getElementById('alpha-asc'); - alpha_desc = document.getElementById('alpha-desc'); - time_asc = document.getElementById('time-asc'); - time_desc = document.getElementById('time-desc'); + const te_python = document.getElementById('pycodetext'); + const te_resulttext = document.getElementById('resulttext'); + const alpha_asc = document.getElementById('alpha-asc'); + const alpha_desc = document.getElementById('alpha-desc'); + const time_asc = document.getElementById('time-asc'); + const time_desc = document.getElementById('time-desc'); + try { + const tooltipList = ['Sort by alphabet ascending', 'Sort by alphabet descending', 'Sort by creationtime ascending', 'Sort by creationtime descending']; + createTooltips(tooltipList); + } + catch (e) {} alpha_asc.addEventListener('click', function() { alpha_desc.classList.remove('active'); time_asc.classList.remove('active'); @@ -450,10 +455,10 @@
{{ _('Plugin') }}     : {% if p.aliv
- - - -
+ + + +
From 25b5417a64d7ff9864ed2e8bac2c9cbd7aa69c5a Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 10 Jul 2023 23:28:26 +0200 Subject: [PATCH 08/16] executor plugin: add two database example scripts --- executor/examples/database_count.py | 9 +++++++++ executor/examples/database_series.py | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 executor/examples/database_count.py create mode 100644 executor/examples/database_series.py diff --git a/executor/examples/database_count.py b/executor/examples/database_count.py new file mode 100644 index 000000000..3d80771a5 --- /dev/null +++ b/executor/examples/database_count.py @@ -0,0 +1,9 @@ +from lib.item import Items +items = Items.get_instance() +myfiller = " " +allItems = items.return_items() +for myItem in allItems: + if not hasattr(myItem,'db'): + continue + mycount = myItem.db('countall', 0) + print (myItem.property.name + myfiller[0:len(myfiller)-len(myItem.property.name)]+ ' - Anzahl Datensätze :'+str(mycount)) diff --git a/executor/examples/database_series.py b/executor/examples/database_series.py new file mode 100644 index 000000000..88a4bcbe3 --- /dev/null +++ b/executor/examples/database_series.py @@ -0,0 +1,9 @@ +import json + +def myconverter(o): +import datetime +if isinstance(o, datetime.datetime): + return o.__str__() +data = sh..series('max','1d','now') +pretty = json.dumps(data, default = myconverter, indent = 2, separators=(',', ': ')) +print(pretty) From f1df7d607e133e4a0be47cb73fe4af153a29ae2f Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 10 Jul 2023 23:29:51 +0200 Subject: [PATCH 09/16] executor plugin: introduce example scripts that can be loaded in the web interface --- executor/webif/__init__.py | 43 ++++++++++++++++--------- executor/webif/templates/index.html | 49 +++++++++++++++++------------ 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/executor/webif/__init__.py b/executor/webif/__init__.py index 30b6b70c5..c964179dd 100755 --- a/executor/webif/__init__.py +++ b/executor/webif/__init__.py @@ -233,15 +233,19 @@ def get_code(self, filename=''): """loads and returns the given filename from the defined script path""" self.logger.debug(f"get_code called with {filename=}") try: - if self.plugin.executor_scripts is not None and filename != '': - filepath = os.path.join(self.plugin.executor_scripts,filename) - self.logger.debug(f"{filepath=}") + if (self.plugin.executor_scripts is not None and filename != '') or filename.startswith('examples/'): + if filename.startswith('examples/'): + filepath = os.path.join(self.plugin.get_plugin_dir(),filename) + self.logger.debug(f"Getting file from example path {filepath=}") + else: + filepath = os.path.join(self.plugin.executor_scripts,filename) + self.logger.debug(f"Getting file from script path {filepath=}") code_file = open(filepath) data = code_file.read() code_file.close() return data - except: - self.logger.error(f"{filepath} could not be read") + except Exception as e: + self.logger.error(f"{filepath} could not be read: {e}") return f"### {filename} could not be read ###" @cherrypy.expose @@ -283,19 +287,28 @@ def delete_file(self, filename=''): @cherrypy.expose def get_filelist(self): - """returns all filenames from the defined script path with suffix ``.py``""" - + """returns all filenames from the defined script path with suffix ``.py``, newest first""" + files = [] + files2 = [] + subdir = "{}/examples".format(self.plugin.get_plugin_dir()) + self.logger.debug(f"list files in plugin examples {subdir}") + mtime = lambda f: os.stat(os.path.join(subdir, f)).st_mtime + files = list(reversed(sorted(os.listdir(subdir), key=mtime))) + files = [f for f in files if os.path.isfile(os.path.join(subdir,f))] + files = ["examples/{}".format(f) for f in files if f.endswith(".py")] + #files = '\n'.join(f for f in files) + self.logger.debug(f"Examples Scripts {files}") if self.plugin.executor_scripts is not None: subdir = self.plugin.executor_scripts self.logger.debug(f"list files in {subdir}") - files = os.listdir(subdir) - files = [f for f in files if os.path.isfile(os.path.join(subdir,f))] - files = [f for f in files if f.endswith(".py")] - files = '\n'.join(f for f in files) - self.logger.debug(f"{files=}\n\n") - return files - - return '' + files2 = list(reversed(sorted(os.listdir(subdir), key=mtime))) + files2 = [f for f in files2 if os.path.isfile(os.path.join(subdir,f))] + files2 = [f for f in files2 if f.endswith(".py")] + #files = '\n'.join(f for f in files) + self.logger.debug(f"User scripts {files2}") + + return json.dumps(files + files2) + @cherrypy.expose def get_autocomplete(self): diff --git a/executor/webif/templates/index.html b/executor/webif/templates/index.html index 5a6b4a4d8..fbf7bc3ea 100755 --- a/executor/webif/templates/index.html +++ b/executor/webif/templates/index.html @@ -34,26 +34,31 @@ function get_filelist(order) { console.log("getting list of files with order " + order); $.get('get_filelist', {}, function(data){ - $('#filelist').empty(); - var lines; - if (order == 'alpha-asc') - lines = data.split(/\r\n|\n\r|\n|\r/).sort(); - else if (order == 'alpha-desc') - lines = data.split(/\r\n|\n\r|\n|\r/).sort().reverse(); - else if (order == 'time-desc') - lines = data.split(/\r\n|\n\r|\n|\r/).reverse(); - else - lines = data.split(/\r\n|\n\r|\n|\r/); - for (line in lines) { - $('#filelist').append(new Option(lines[line], lines[line])); - }; - var size; - if (max_script_entries == 0) { size = 0; } - else if (lines.length > max_script_entries) { size = max_script_entries; } - else { size = lines.length; }; - console.log("Size: ",size); - $('#filelist').attr('size',size); - console.log('Data:'+data); + $('#filelist').empty(); + var lines; + data = JSON.parse(data); + if (order == 'alpha-asc') + lines = data.sort(); + else if (order == 'alpha-desc') + lines = data.sort().reverse(); + else if (order == 'time-desc') + lines = data.reverse(); + else + lines = data; + for (line in lines) { + $('#filelist').append(new Option(lines[line], lines[line])); + }; + $('#filelist > option').each(function(){ + if ($(this).attr('value').indexOf('examples/') == 0) + $(this).addClass('example_file'); + }); + var size; + if (max_script_entries == 0) { size = 0; } + else if (lines.length > max_script_entries) { size = max_script_entries; } + else { size = lines.length; }; + console.log("Size: " + size); + console.log('Data: ' + data); + $('#filelist').attr('size',size); }); }; @@ -416,6 +421,10 @@ border: none; filter: invert(8%) sepia(100%) saturate(6481%) hue-rotate(246deg) brightness(102%) contrast(143%); } +.example_file { + font-style: italic; + color: grey; +} {% endblock pluginstyles %} {% block content -%} From 81326c1f5ae2f392c24625e4adad4acd4f64c1e5 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 10 Jul 2023 23:36:53 +0200 Subject: [PATCH 10/16] executor plugin: change order of file list --- executor/webif/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/webif/__init__.py b/executor/webif/__init__.py index c964179dd..26a6f9e96 100755 --- a/executor/webif/__init__.py +++ b/executor/webif/__init__.py @@ -307,7 +307,7 @@ def get_filelist(self): #files = '\n'.join(f for f in files) self.logger.debug(f"User scripts {files2}") - return json.dumps(files + files2) + return json.dumps(files2 + files) @cherrypy.expose From dbeb6440822fc77253319e634d3af31ec642eabb Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 10 Jul 2023 23:38:01 +0200 Subject: [PATCH 11/16] executor plugin: update user_doc --- executor/user_doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/user_doc.rst b/executor/user_doc.rst index 964d35c99..53238c335 100755 --- a/executor/user_doc.rst +++ b/executor/user_doc.rst @@ -16,7 +16,7 @@ executor Einführung ~~~~~~~~~~ -Das executor Plugin kann genutzt werden, um **Python Code** (z.B. für **Logiken**) und **eval Ausdrücke** zu testen. +Das executor Plugin kann genutzt werden, um **Python Code** (z.B. für **Logiken**) zu testen. .. important:: From 24b3c985a285db7939425113abc9bd3b801fa602 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Mon, 10 Jul 2023 23:41:18 +0200 Subject: [PATCH 12/16] executor plugin: add new log levels such as notice, dbglow, etc. --- executor/webif/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/executor/webif/__init__.py b/executor/webif/__init__.py index 26a6f9e96..512d899c9 100755 --- a/executor/webif/__init__.py +++ b/executor/webif/__init__.py @@ -201,7 +201,7 @@ def exec_code(self, eline, reload=None): :return: result of the evaluation """ result = "" - stub_logger = Stub(warning=print, info=print, debug=print, error=print) + stub_logger = Stub(warning=print, info=print, debug=print, error=print, criticl=print, notice=print, dbghigh=print, dbgmed=print, dbglow=print) g = {} l = { 'sh': self.plugin.get_sh(), From 231f5ac3489da366058218d6ee12454de7f956a6 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 11 Jul 2023 10:18:57 +0200 Subject: [PATCH 13/16] executor plugin: improve button enable/disable --- executor/webif/templates/index.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/executor/webif/templates/index.html b/executor/webif/templates/index.html index fbf7bc3ea..371ca5c99 100755 --- a/executor/webif/templates/index.html +++ b/executor/webif/templates/index.html @@ -65,6 +65,14 @@ function selectFile(selectObject) { var value = selectObject.value; $('#savefilename').val(value); + const del_button = document.querySelector('#deletefile'); + const load_button = document.querySelector('#loadfilename'); + load_button.removeAttribute("disabled"); + if (value.startsWith("examples/")) + del_button.setAttribute("disabled", "disabled"); + else + del_button.removeAttribute("disabled"); + console.log("selected ",value); }; @@ -240,6 +248,10 @@ $(document).ready(function(){ + const load_button = document.querySelector('#loadfilename'); + const del_button = document.querySelector('#deletefile'); + load_button.setAttribute("disabled", "disabled"); + del_button.setAttribute("disabled", "disabled"); const te_python = document.getElementById('pycodetext'); const te_resulttext = document.getElementById('resulttext'); const alpha_asc = document.getElementById('alpha-asc'); From c62414eb929861a97a78a19758210abbbc391172 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Tue, 11 Jul 2023 22:24:21 +0200 Subject: [PATCH 14/16] executor plugin: add additional examples --- executor/examples/check_device_presence.py | 14 ++++ executor/examples/check_items.py | 95 ++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 executor/examples/check_device_presence.py create mode 100644 executor/examples/check_items.py diff --git a/executor/examples/check_device_presence.py b/executor/examples/check_device_presence.py new file mode 100644 index 000000000..c7dfcc743 --- /dev/null +++ b/executor/examples/check_device_presence.py @@ -0,0 +1,14 @@ +import os + +with os.popen('ip neigh show') as result: + # permanent, noarp, reachable, stale, none, incomplete, delay, probe, failed + ip = '192.168.10.56' + mac = "b4:b5:2f:ce:6d:29" + value = False + lines = str(result.read()).splitlines() + for line in lines: + if (ip in line or mac in line) and ("REACHABLE" in line or "STALE" in line): + value = True + break + #sh.devices.laptop.status(value)​ + print(f"set item to {value}") \ No newline at end of file diff --git a/executor/examples/check_items.py b/executor/examples/check_items.py new file mode 100644 index 000000000..8a9a7fb07 --- /dev/null +++ b/executor/examples/check_items.py @@ -0,0 +1,95 @@ +""" +given following items within a yaml: + + +MyItem: + MyChildItem: + type: num + initial_value: 12 + MyGrandchildItem: + type: str + initial_value: "foo" + +Within a logic it is possible to set the value of MyChildItem to 42 with +``sh.MyItem.MyChildItem(42)`` and retrieve the Items value with +``value = sh.MyItem.MyChildItem()`` + +Often beginners forget the parentheses and instead write +``sh.MyItem.MyChildItem = 42`` when they really intend to assign the value ``42`` +to the item or write ``value = sh.MyItem.MyChildItem`` when they really want to +retrieve the item's value. + +But using ``sh.MyItem.MyChildItem = 42`` destroys the structure here and makes +it impossible to retrieve the value of the child +``MyItem.MyChildItem.MyGrandchildItem`` +Alike, an instruction as ``value = sh.MyItem.MyChildItem`` will not assign the +value of ``sh.MyItem.MyChildItem`` but assign a reference to the item object +``sh.MyItem.MyChildItem`` + +It is not possible with Python to intercept an assignment to a variable or an +objects' attribute. The only thing one can do is search all items for a +mismatching item type. + +This logic checks all items returned by SmartHomeNG, and if it encounters one +which seems to be damaged like described before, it attempts to repair the +broken assignment. + +""" +from lib.item import Items +from lib.item.item import Item + +def repair_item(sh, item): + path = item.id() + path_elems = path.split('.') + ref = sh + + # traverse through object structure sh.path1.path2... + try: + for path_part in path_elems[:-1]: + ref = getattr(ref, path_part) + + setattr(ref, path_elems[-1], item) + print(f'Item reference repaired for {path}') + return True + except NameError: + print(f'Error: item traversal for {path} failed at part {path_part}. Item list not sorted?') + + return False + + +def get_item_type(sh, path): + expr = f'type(sh.{path})' + return str(eval(expr)) + + +def check_item(sh, path): + + return isinstance(path, Item) + + +# to get access to the object instance: +items = Items.get_instance() + +# to access a method (eg. to get the list of Items): +# allitems = items.return_items() +problems_found = 0 +problems_fixed = 0 +itemClass = Item + +for one in items.return_items(ordered=True): + # get the items full path + path = one.property.path + try: + if not isinstance(one, itemclass): + logger.error(f"Error: item {path} has type but should be an Item Object") + problems_found += 1 + if repair_item(sh, one): + if check_item(sh, path): + problems_fixed += 1 + except ValueError as e: + logger.error(f'Error {e} while processing item {path}, parent defective? Items not sorted?') + +if problems_found: + logger.error(f"{problems_found} problematic item assignment{'' if problems_found == 1 else 's'} found, {problems_fixed} item assignment{'' if problems_fixed == 1 else 's'} fixed") +else: + logger.warning("no problems found") From c4a46c5285c90c03c9837492144c75d3b73deccd Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 13 Jul 2023 08:45:45 +0200 Subject: [PATCH 15/16] executor plugin: minor adjustment for button disable/input field clear handling --- executor/webif/templates/index.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/executor/webif/templates/index.html b/executor/webif/templates/index.html index 371ca5c99..bda07578b 100755 --- a/executor/webif/templates/index.html +++ b/executor/webif/templates/index.html @@ -111,7 +111,11 @@ console.log('file to delete'+filenametodelete); $.get('delete_file', { filename: filenametodelete}, function(result) { console.log('Result:'+result); - get_filelist(getCookie('sort_order')); + const save_input = document.querySelector('#savefilename'); + const del_button = document.querySelector('#deletefile'); + del_button.setAttribute("disabled", "disabled"); + save_input.value=""; + get_filelist(getCookie('sort_order')); }); }; From e23bf29effe9a5dbca78ac5d28bc8a948eccf9a8 Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Thu, 13 Jul 2023 14:58:31 +0200 Subject: [PATCH 16/16] executor plugin: fix check_items example --- executor/examples/check_items.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/executor/examples/check_items.py b/executor/examples/check_items.py index 8a9a7fb07..e41eb3316 100644 --- a/executor/examples/check_items.py +++ b/executor/examples/check_items.py @@ -63,8 +63,9 @@ def get_item_type(sh, path): def check_item(sh, path): + global get_item_type - return isinstance(path, Item) + return get_item_type(sh, path) == "" # to get access to the object instance: @@ -74,14 +75,13 @@ def check_item(sh, path): # allitems = items.return_items() problems_found = 0 problems_fixed = 0 -itemClass = Item for one in items.return_items(ordered=True): # get the items full path path = one.property.path try: - if not isinstance(one, itemclass): - logger.error(f"Error: item {path} has type but should be an Item Object") + if not check_item(sh, path): + logger.error(f"Error: item {path} has type {get_item_type(sh, path)} but should be an Item Object") problems_found += 1 if repair_item(sh, one): if check_item(sh, path):