Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DBADDON Plugin: #859

Merged
merged 18 commits into from
Nov 30, 2023
2,034 changes: 1,173 additions & 861 deletions db_addon/__init__.py
100755 → 100644

Large diffs are not rendered by default.

52 changes: 33 additions & 19 deletions db_addon/item_attributes.py

Large diffs are not rendered by default.

455 changes: 313 additions & 142 deletions db_addon/item_attributes_master.py
100755 → 100644

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions db_addon/locale.yaml
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# translations for the web interface
plugin_translations:
# Translations for the plugin specially for the web interface
'hourly': {'de': 'stündlich', 'en': 'hourly'}
'daily': {'de': 'täglich', 'en': 'daily'}
'weekly': {'de': 'wöchentlich', 'en': '='}
'monthly': {'de': 'monatlich', 'en': '='}
Expand Down
470 changes: 322 additions & 148 deletions db_addon/plugin.yaml
100755 → 100644

Large diffs are not rendered by default.

Empty file modified db_addon/requirements.txt
100755 → 100644
Empty file.
68 changes: 36 additions & 32 deletions db_addon/user_doc.rst
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ermittelt.
Alternativ kann mit dem Attribute "db_addon_database_item" auch der absolute Pfad des Items angegeben werden, für das
das Database Attribut konfiguriert ist.

Bsp:
Beispiel:


.. code-block:: yaml
Expand Down Expand Up @@ -97,37 +97,36 @@ Dazu folgenden Block am Ende der Datei */etc/mysql/my.cnf* einfügen bzw den exi
Hinweise
========

- Das Plugin startet die Berechnungen der Werte nach einer gewissen (konfigurierbaren) Zeit (Attribut `startup_run_delay`)
nach dem Start von shNG, um den Startvorgang nicht zu beeinflussen.
- Das Plugin startet die Berechnungen der Werte nach einer gewissen (konfigurierbaren) Zeit (Attribut `startup_run_delay`)
nach dem Start von shNG, um den Startvorgang nicht zu beeinflussen.

- Bei Start werden automatisch nur die Items berechnet, für das das Attribute `db_addon_startup` gesetzt wurde. Alle anderen
Items werden erst zur konfigurierten Zeit berechnet. Das Attribute `db_addon_startup` kann auch direkt am `Database-Item`
gesetzt werden. Dabei wird das Attribut auf alle darunter liegenden `db_addon-Items` (bspw. bei Verwendung von structs) vererbt.
Über das WebIF kann die Berechnung aller definierten Items ausgelöst werden.
- Bei Start werden automatisch nur die Items berechnet, für das das Attribute `db_addon_startup` gesetzt wurde. Alle anderen
Items werden erst zur konfigurierten Zeit berechnet. Das Attribute `db_addon_startup` kann auch direkt am `Database-Item`
gesetzt werden. Dabei wird das Attribut auf alle darunter liegenden `db_addon-Items` (bspw. bei Verwendung von structs) vererbt.
Über das WebIF kann die Berechnung aller definierten Items ausgelöst werden.

- Für sogenannte `on_change` Items, also Items, deren Berechnung bis zum Jetzt (bspw. verbrauch-heute) gehen, wird die Berechnung
immer bei eintreffen eines neuen Wertes gestartet. Zu Reduktion der Belastung auf die Datenbank werden die Werte für das Ende der
letzten Periode gecached.
- Für sogenannte `on_change` Items, also Items, deren Berechnung bis zum Jetzt (bspw. verbrauch-heute) gehen, wird die Berechnung
immer bei eintreffen eines neuen Wertes gestartet. Zu Reduktion der Belastung auf die Datenbank werden die Werte für das Ende der
letzten Periode gecached.

- Berechnungen werden nur ausgeführt, wenn für den kompletten abgefragten Zeitraum Werte in der Datenbank vorliegen. Wird bspw.
der Verbrauch des letzten Monats abgefragt wobei erst Werte ab dem 3. des Monats in der Datenbank sind, wird die Berechnung abgebrochen.
- Berechnungen werden nur ausgeführt, wenn für den kompletten abgefragten Zeitraum Werte in der Datenbank vorliegen. Wird bspw.
der Verbrauch des letzten Monats abgefragt wobei erst Werte ab dem 3. des Monats in der Datenbank sind, wird die Berechnung abgebrochen.

- Mit dem Attribut `use_oldest_entry` kann dieses Verhalten verändert werden. Ist das Attribut gesetzt, wird, wenn für den
Beginn der Abfragezeitraums keinen Werte vorliegen, der älteste Eintrag der Datenbank genutzt.
- Mit dem Attribut `use_oldest_entry` kann dieses Verhalten verändert werden. Ist das Attribut gesetzt, wird, wenn für den
Beginn der Abfragezeitraums keinen Werte vorliegen, der älteste Eintrag der Datenbank genutzt.

- Für die Auswertung kann es nützlich sein, bestimmte Werte aus der Datenbank bei der Berechnung auszublenden. Hierfür stehen
2 Möglichkeiten zur Verfügung:
- Für die Auswertung kann es nützlich sein, bestimmte Werte aus der Datenbank bei der Berechnung auszublenden. Hierfür stehen 2 Möglichkeiten zur Verfügung:
- Plugin-Attribut `ignore_0`: (list of strings) Bei Items, bei denen ein String aus der Liste im Pfadnamen vorkommt,
werden 0-Werte (val_num = 0) bei Datenbankauswertungen ignoriert. Hat also das Attribut den Wert ['temp'] werden bei allen Items mit
'temp' im Pfadnamen die 0-Werte bei der Auswertung ignoriert.
- Item-Attribut `db_addon_ignore_value`: (num) Dieser Wert wird bei der Abfrage bzw. Auswertung der Datenbank für diese
Item ignoriert.

- Das Plugin enthält sehr ausführliche Logginginformation. Bei unerwartetem Verhalten, den LogLevel entsprechend anpassen,
um mehr information zu erhalten.
- Item-Attribut `db_addon_ignore_value`: (num) Dieser Wert wird bei der Abfrage bzw. Auswertung der Datenbank für dieses Item ignoriert.

- Berechnungen des Plugins können im WebIF unterbrochen werden. Auch das gesamte Plugin kann pausiert werden. Dies kann bei
starker Systembelastung nützlich sein.
- Das Plugin enthält sehr ausführliche Logginginformation. Bei unerwartetem Verhalten, den LogLevel entsprechend anpassen,
um mehr information zu erhalten.

- Berechnungen des Plugins können im WebIF unterbrochen werden. Auch das gesamte Plugin kann pausiert werden. Dies kann bei
starker Systembelastung nützlich sein.


Beispiele
Expand All @@ -140,6 +139,7 @@ Soll bspw. der Verbrauch von Wasser ausgewertet werden, so ist dies wie folgt m


.. code-block:: yaml

wasserzaehler:
zaehlerstand:
type: num
Expand All @@ -161,6 +161,7 @@ minmax
Soll bspw. die minimalen und maximalen Temperaturen ausgewertet werden, kann dies so umgesetzt werden:

.. code-block:: yaml

temperature:
aussen:
nord:
Expand All @@ -177,7 +178,6 @@ Soll bspw. die minimalen und maximalen Temperaturen ausgewertet werden, kann die
Die Temperaturwerte werden in die Datenbank geschrieben und darauf basierend ausgewertet. Die structs
'db_addon.minmax_1' und 'db_addon.minmax_2' stellen entsprechende Items für die min/max Auswertung zur Verfügung.

|

Web Interface
=============
Expand Down Expand Up @@ -218,8 +218,8 @@ Erläuterungen zu Temperatursummen
Grünlandtemperatursumme
-----------------------

Beim Grünland wird die Wärmesumme nach Ernst und Loeper benutzt, um den Vegetationsbeginn und somit den Termin von Düngungsmaßnahmen zu bestimmen.
Dabei erfolgt die Aufsummierung der Tagesmitteltemperaturen über 0 °C, wobei der Januar mit 0.5 und der Februar mit 0.75 gewichtet wird.
Beim Grünland wird die Wärmesumme nach Ernst und Loeper benutzt, um den Vegetationsbeginn und somit den Termin von Düngungsmaßnahmen zu bestimmen.
Dabei erfolgt die Aufsummierung der Tagesmitteltemperaturen über 0 °C, wobei der Januar mit 0.5 und der Februar mit 0.75 gewichtet wird.
Bei einer Wärmesumme von 200 Grad ist eine Düngung angesagt.

siehe: https://de.wikipedia.org/wiki/Gr%C3%BCnlandtemperatursumme
Expand All @@ -228,43 +228,46 @@ Folgende Parameter sind möglich / notwendig:


.. code-block:: yaml

db_addon_params: "year=current"

- year: Jahreszahl (str oder int), für das die Berechnung ausgeführt werden soll oder "current" für aktuelles Jahr (default: 'current')


Wachstumsgradtag
----------------
Der Begriff Wachstumsgradtage (WGT) ist ein Überbegriff für verschiedene Größen.
Gemeinsam ist ihnen, daß zur Berechnung eine Lufttemperatur von einem Schwellenwert subtrahiert wird.
Je nach Fragestellung und Pflanzenart werden der Schwellenwert unterschiedlich gewählt und die Temperatur unterschiedlich bestimmt.
Verfügbar sind die Berechnung über 0) "einfachen Durchschnitt der Tagestemperaturen", 1) "modifizierten Durchschnitt der Tagestemperaturen"
Der Begriff Wachstumsgradtage (WGT) ist ein Überbegriff für verschiedene Größen.
Gemeinsam ist ihnen, daß zur Berechnung eine Lufttemperatur von einem Schwellenwert subtrahiert wird.
Je nach Fragestellung und Pflanzenart werden der Schwellenwert unterschiedlich gewählt und die Temperatur unterschiedlich bestimmt.
Verfügbar sind die Berechnung über 0) "einfachen Durchschnitt der Tagestemperaturen", 1) "modifizierten Durchschnitt der Tagestemperaturen"
und 2) Anzahl der Tage, deren Mitteltempertatur oberhalb der Schwellentemperatur lag.

siehe https://de.wikipedia.org/wiki/Wachstumsgradtag

Folgende Parameter sind möglich / notwendig:

.. code-block:: yaml

db_addon_params: "year=current, method=1, threshold=10"

- year: Jahreszahl (str oder int), für das die Berechnung ausgeführt werden soll oder "current" für aktuelles Jahr (default: 'current')
- method: 0-Berechnung über "einfachen Durchschnitt der Tagestemperaturen", 1-Berechnung über "modifizierten Durchschnitt (default: 0)
der Tagestemperaturen" 2-Anzahl der Tage, mit Mitteltempertatur oberhalb Schwellentemperatur// 10, 11 Ausgabe aus Zeitserie
der Tagestemperaturen" 2-Anzahl der Tage, mit Mitteltempertatur oberhalb Schwellentemperatur// 10, 11 Ausgabe aus Zeitserie
- threshold: Schwellentemperatur in °C (int) (default: 10)


Wärmesumme
----------

Die Wärmesumme soll eine Aussage über den Sommer und die Pflanzenreife liefern. Es gibt keine eindeutige Definition der Größe "Wärmesumme".
Berechnet wird die Wärmesumme als Summe aller Tagesmitteltemperaturen über einem Schwellenwert ab dem 1.1. des Jahres.
Berechnet wird die Wärmesumme als Summe aller Tagesmitteltemperaturen über einem Schwellenwert ab dem 1.1. des Jahres.

siehe https://de.wikipedia.org/wiki/W%C3%A4rmesumme

Folgende Parameter sind möglich / notwendig:

.. code-block:: yaml

db_addon_params: "year=current, month=1, threshold=10"

- year: Jahreszahl (str oder int), für das die Berechnung ausgeführt werden soll oder "current" für aktuelles Jahr (default: 'current')
Expand All @@ -275,14 +278,15 @@ Folgende Parameter sind möglich / notwendig:
Kältesumme
----------

Die Kältesumme soll eine Aussage über die Härte des Winters liefern.
Die Kältesumme soll eine Aussage über die Härte des Winters liefern.
Berechnet wird die Kältesumme als Summe aller negativen Tagesmitteltemperaturen ab dem 21.9. des Jahres bis 31.3. des Folgejahres.

siehe https://de.wikipedia.org/wiki/K%C3%A4ltesumme

Folgende Parameter sind möglich / notwendig:

.. code-block:: yaml

db_addon_params: "year=current, month=1"

- year: Jahreszahl (str oder int), für das die Berechnung ausgeführt werden soll oder "current" für aktuelles Jahr (default: 'current')
Expand Down
142 changes: 134 additions & 8 deletions db_addon/webif/__init__.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def __init__(self, webif_dir, plugin):
self.tplenv = self.init_template_environment()

@cherrypy.expose
def index(self, reload=None):
def index(self, reload=None, action=None, item_path=None, active=None, option=None):
"""
Build index.html for cherrypy

Expand All @@ -68,17 +68,31 @@ def index(self, reload=None):
:return: contents of the template after being rendered
"""

pagelength = self.plugin.get_parameter_value('webif_pagelength')
tmpl = self.tplenv.get_template('index.html')

if action is not None:
if action == "recalc_item" and item_path is not None:
self.logger.info(f"Recalc of item={item_path} called via WebIF. Item put to Queue for new calculation.")
self.plugin.execute_items(option='item', item=item_path)

elif action == "clean_item_cache" and item_path is not None:
self.logger.info(f"Clean item cache of item={item_path} called via WebIF. Plugin item value cache will be cleaned.")
self.plugin._clean_item_cache(item=item_path)

elif action == "_activate_item_calculation" and item_path is not None and active is not None:
self.logger.info(f"Item calculation of item={item_path} will be set to {bool(int(active))} via WebIF.")
self.plugin._activate_item_calculation(item=item_path, active=bool(int(active)))

return tmpl.render(p=self.plugin,
webif_pagelength=self.plugin.get_parameter_value('webif_pagelength'),
webif_pagelength=pagelength,
suspended='true' if self.plugin.suspended else 'false',
items=self.plugin.get_item_list('db_addon', 'function'),
item_count=len(self.plugin.get_item_list('db_addon', 'function')),
plugin_shortname=self.plugin.get_shortname(),
plugin_version=self.plugin.get_version(),
plugin_info=self.plugin.get_info(),
maintenance=True if self.plugin.log_level == 10 else False,
maintenance=True if self.plugin.log_level < 20 else False,
)

@cherrypy.expose
Expand All @@ -94,24 +108,70 @@ def get_data_html(self, dataSet=None):
if dataSet is None:
# get the new data
data = dict()
data['items'] = {}

data['items'] = {}
for item in self.plugin.get_item_list('db_addon', 'function'):
data['items'][item.id()] = {}
data['items'][item.id()]['value'] = item.property.value
data['items'][item.id()]['last_update'] = item.property.last_update.strftime('%d.%m.%Y %H:%M:%S')
data['items'][item.id()]['last_change'] = item.property.last_change.strftime('%d.%m.%Y %H:%M:%S')
data['items'][item.path()] = {}
data['items'][item.path()]['value'] = item.property.value
data['items'][item.path()]['last_update'] = item.property.last_update.strftime('%d.%m.%Y %H:%M:%S')
data['items'][item.path()]['last_change'] = item.property.last_change.strftime('%d.%m.%Y %H:%M:%S')

data['plugin_suspended'] = self.plugin.suspended
data['maintenance'] = True if self.plugin.log_level == 10 else False
data['queue_length'] = self.plugin.queue_backlog()
data['active_queue_item'] = self.plugin.active_queue_item

data['debug_log'] = {}
for debug in ['parse', 'execute', 'ondemand', 'onchange', 'prepare', 'sql']:
data['debug_log'][debug] = getattr(self.plugin.debug_log, debug)

try:
return json.dumps(data, default=str)
except Exception as e:
self.logger.error(f"get_data_html exception: {e}")

@cherrypy.expose
def submit(self, item=None):
result = None
item_path, cmd = item.split(':')
if item_path is not None and cmd is not None:
self.logger.debug(f"Received db_addon {cmd=} for {item_path=} via web interface")

if cmd == "recalc_item":
self.logger.info(f"Recalc of item={item_path} called via WebIF. Item put to Queue for new calculation.")
result = self.plugin.execute_items(option='item', item=item_path)
self.logger.debug(f"Result for web interface: {result}")
return json.dumps(result).encode('utf-8')

elif cmd == "clean_item_cache":
self.logger.info(f"Clean item cache of item={item_path} called via WebIF. Plugin item value cache will be cleaned.")
result = self.plugin._clean_item_cache(item=item_path)
self.logger.debug(f"Result for web interface: {result}")
return json.dumps(result).encode('utf-8')

elif cmd.startswith("suspend_plugin_calculation"):
self.logger.debug(f"set_plugin_calculation {cmd=}")
cmd, value = cmd.split(',')
value = True if value == "True" else False
self.logger.info(f"Plugin will be set to suspended: {value} via WebIF.")
result = self.plugin.suspend(value)
self.logger.debug(f"Result for web interface: {result}")
return json.dumps(result).encode('utf-8')

elif cmd.startswith("suspend_item_calculation"):
cmd, value = cmd.split(',')
self.logger.info(f"Item calculation of item={item_path} will be set to suspended: {value} via WebIF.")
value = True if value == "True" else False
result = self.plugin._suspend_item_calculation(item=item_path, suspended=value)
self.logger.debug(f"Result for web interface: {result}")
return json.dumps(result).encode('utf-8')

if result is not None:
# JSON zurücksenden
cherrypy.response.headers['Content-Type'] = 'application/json'
self.logger.debug(f"Result for web interface: {result}")
return json.dumps(result).encode('utf-8')

@cherrypy.expose
def recalc_all(self):
self.logger.debug(f"recalc_all called")
Expand All @@ -136,3 +196,69 @@ def activate(self):
def suspend(self):
self.logger.debug(f"suspend called")
self.plugin.suspend(True)

@cherrypy.expose
def debug_log_option(self, log: str = None, state: bool = None):
self.logger.warning(f"debug_log_option called with {log=}, {state=}")
_state = True if state == 'true' else False
setattr(self.plugin.debug_log, log, _state)

@cherrypy.expose
def debug_log_option_parse_true(self):
self.logger.debug("debug_log_option_parse_true")
setattr(self.plugin.debug_log, 'parse', True)

@cherrypy.expose
def debug_log_option_parse_false (self):
self.logger.debug("debug_log_option_parse_false")
setattr(self.plugin.debug_log, 'parse', False)

@cherrypy.expose
def debug_log_option_execute_true(self):
self.logger.debug("debug_log_option_execute_true")
setattr(self.plugin.debug_log, 'execute', True)

@cherrypy.expose
def debug_log_option_execute_false (self):
self.logger.debug("debug_log_option_execute_false")
setattr(self.plugin.debug_log, 'execute', False)

@cherrypy.expose
def debug_log_option_ondemand_true(self):
self.logger.debug("debug_log_option_ondemand_true")
setattr(self.plugin.debug_log, 'ondemand', True)

@cherrypy.expose
def debug_log_option_ondemand_false (self):
self.logger.debug("debug_log_option_ondemand_false")
setattr(self.plugin.debug_log, 'ondemand', False)

@cherrypy.expose
def debug_log_option_onchange_true(self):
self.logger.debug("debug_log_option_onchange_true")
setattr(self.plugin.debug_log, 'onchange', True)

@cherrypy.expose
def debug_log_option_onchange_false (self):
self.logger.debug("debug_log_option_onchange_false")
setattr(self.plugin.debug_log, 'onchange', False)

@cherrypy.expose
def debug_log_option_prepare_true(self):
self.logger.debug("debug_log_option_prepare_true")
setattr(self.plugin.debug_log, 'prepare', True)

@cherrypy.expose
def debug_log_option_prepare_false (self):
self.logger.debug("debug_log_option_prepare_false")
setattr(self.plugin.debug_log, 'prepare', False)

@cherrypy.expose
def debug_log_option_sql_true(self):
self.logger.debug("debug_log_option_sql_true")
setattr(self.plugin.debug_log, 'sql', True)

@cherrypy.expose
def debug_log_option_sql_false (self):
self.logger.debug("debug_log_option_sql_false")
setattr(self.plugin.debug_log, 'sql', False)
Empty file modified db_addon/webif/static/img/plugin_logo.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading