From f52f0f0f748eddcc0fe959c6c1e8cbfbf6e8d511 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Thu, 14 Dec 2023 18:14:20 +0100 Subject: [PATCH 001/112] prepare new develop --- changelog.md | 20 ++++++++++++++++++++ readme.md | 6 +++++- version-info.php | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index be13e915..54d64111 100755 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,23 @@ +## 3.4.a +### New / Changed Widgets + +### Other New Features + +### Improvements + +### Updated Libraries + +### Deprecated + +### Removed Features + +### Fixed Bugs + +### Known Bugs +- if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text +- smartVISU versions 3.3.1 and older display incorrect version info in the update messages since the deprecated format has been removed from version-info.php + + ## 3.4 ### New / Changed Widgets - quad.blind and quad.shutter can be configured to move the shutter on short- or longpress diff --git a/readme.md b/readme.md index 704a7e74..dd197458 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,10 @@ [![Join the chat at https://gitter.im/sVISU/Lobby](https://badges.gitter.im/sVISU/Lobby.svg)](https://gitter.im/sVISU/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +## Warning ! The current version in this develop branch is experimental and should not be used in productive environments! +Use version v3.4 from master branch instead. + + ## DESCRIPTION smartVISU is a framework to create a visualisation for a knx-installation with simple html-pages. To read and write group-telegrams special tags are used. @@ -22,7 +26,7 @@ SEE: [smartvisu.de](http://www.smartvisu.de) ## SYSTEM REQUIREMENTS * IP-Network, KNX-Bus * [smarthomeNG](https://github.com/smarthomeNG), [linknx](http://sourceforge.net/projects/linknx/), [ioBroker](https://github.com/ioBroker/ioBroker), [openHAB](https://www.openhab.org/), [FHEM](https://fhem.de/) or [knxd](https://github.com/knxd/knxd) (deprecated: [eibd](http://www.auto.tuwien.ac.at/~mkoegler/index.php/eibd) ) backend or JSON interface - * Webserver with PHP 7.3.2 and above. Compatibility with php v8.2 is verified. PHP packages required: libawl-php php-curl php php-json php-xml php-mbstring + * Webserver with PHP 7.3.2 and above. Compatibility with php v8.0 is verified. PHP packages required: libawl-php php-curl php php-json php-xml php-mbstring * Firefox, Chrome, IE, Safari, iPhone, iPad, Android Phone or Android Tablet diff --git a/version-info.php b/version-info.php index 25341938..03315170 100644 --- a/version-info.php +++ b/version-info.php @@ -2,5 +2,5 @@ define ('config_visu', 'smartVISU'); define ('config_version_major', '3'); define ('config_version_minor', '4'); -define ('config_version_revision', '0'); +define ('config_version_revision', 'a'); ?> From 4cd13d7d6d728a8ed53b0329d038d1843fff366b Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:18:07 +0100 Subject: [PATCH 002/112] dummy infoblock file for snarthome folder --- changelog.md | 1 + pages/smarthome/index.html | 4 ++-- pages/smarthome/infoblock.html | 29 +++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 pages/smarthome/infoblock.html diff --git a/changelog.md b/changelog.md index 54d64111..c844eb30 100755 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ ### Removed Features ### Fixed Bugs +- system page was not shown due to missing infoblock.html if pages were configured to "Smarthome" but pages had not been created yet by the "smartvisu" plugin of smarthomeNG. ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/pages/smarthome/index.html b/pages/smarthome/index.html index 9f18a105..8ffac2ed 100644 --- a/pages/smarthome/index.html +++ b/pages/smarthome/index.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2023 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -43,7 +43,7 @@ {% block content %}

- {% if config_lang != 'de' %} + {% if lang('baselang') != 'de' %} Welcome to smartVISU !

You are just a few steps away from setting up your own visualization.
The folder you have just configured is for pages auto-generated by smarthoneNG or other backend systems. By now, it is still empty.

diff --git a/pages/smarthome/infoblock.html b/pages/smarthome/infoblock.html new file mode 100644 index 00000000..d967ada3 --- /dev/null +++ b/pages/smarthome/infoblock.html @@ -0,0 +1,29 @@ +/** +* ----------------------------------------------------------------------------- +* @package smartVISU +* @author Wolfram v. Hülsen +* @copyright 2021 +* @license GPL [http://www.gnu.de] +* +* dummy info block - to be displayed if user configured infoblock.html is missing +* ----------------------------------------------------------------------------- +*/ + +
+


+{% if lang('baselang') != 'de' %} + The file infoblock.html is missng here.

+ {% if config_pages == 'smarthome' %} + Start the page generation process in smarthomeNG to have the file copied from the _template folder. + {% else %} + Copy the file from the folder ./pages/_template to the folder containig your own pages and cusomize the content to your needs. + {% endif %} +{% else %} + Hier fehlt die Datei infoblock.html.

+ {% if config_pages == 'smarthome' %} + Starte die Autogenerierung der Seiten in smarthomeNG, um eine Kopie aus dem Ordner _template zu erzeugen. + {% else %} + Kopiere die Datei aus dem Ordner ./pages/_template in den Ordner mit Deinen Seiten und passe den Inhalt bei Bedarf nach Deinen Wünschen an. + {% endif %} +{% endif %} +
\ No newline at end of file From b6192d74f29b2c0918cff043ddeac2cddd554e0a Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sat, 16 Dec 2023 12:57:25 +0100 Subject: [PATCH 003/112] fix inconsistent interpolation in uzsugraph --- changelog.md | 3 ++- widgets/device.js | 13 +++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/changelog.md b/changelog.md index c844eb30..e15f3d16 100755 --- a/changelog.md +++ b/changelog.md @@ -12,7 +12,8 @@ ### Removed Features ### Fixed Bugs -- system page was not shown due to missing infoblock.html if pages were configured to "Smarthome" but pages had not been created yet by the "smartvisu" plugin of smarthomeNG. +- system page was not shown due to missing infoblock.html if pages were configured to "Smarthome" but pages had not been created yet by the "smartvisu" plugin of smarthomeNG. +- behaviour of device.uzsugraph interpolation style was inconsistent if more than one uzsugraph widget was on a page. ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/widgets/device.js b/widgets/device.js index d6505eec..99211076 100755 --- a/widgets/device.js +++ b/widgets/device.js @@ -1381,7 +1381,7 @@ $.widget("sv.device_uzsu", $.sv.widget, { } else { this.hasInterpolation = true - console.log('UZSU interpolation set to ' + response.interpolation.type); + console.log('UZSU interpolation for item "' + this.options.item + '" set to ' + response.interpolation.type); } //plugin version in dict has been introduced with the series functionality - no explicit version check necessary if (response.plugin_version === undefined){ @@ -1677,12 +1677,6 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { // init data (used if no update follows because item does not exist yet) this._uzsudata = { active : true, list : [] } -// already called in _create of prototype widget -// this.options.designtype = String(this.options.designtype); -// if(this.options.designtype === undefined || this.options.designtype === '') { -// this.options.designtype = io.uzsu_type; -// } - var valueParameterList = this.options.valueparameterlist.explode(); if(valueParameterList.length === 0){ if(this.options.valuetype === 'bool') valueParameterList = ['1', '0', '1']; @@ -2078,7 +2072,7 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { var hasBurst = false; var seriesData = { active: [], inactive: [], range: [] }; var linetype = this._uzsudata.interpolation.type == 'cubic' ? 'spline' : 'line'; - Highcharts.seriesTypes.scatter.prototype.getPointSpline = Highcharts.seriesTypes[linetype].prototype.getPointSpline; + // Highcharts.seriesTypes.scatter.prototype.getPointSpline = Highcharts.seriesTypes[linetype].prototype.getPointSpline; // as from v3.2 we start with "today" instead of Monday var today = new Date().getDay(); // delivers SU = 0 @@ -2194,7 +2188,7 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { chart.get('active').setData(data, false, null, false); chart.get('active').update({ - type: 'scatter', + type: this._uzsudata.interpolation.type == 'cubic' ? 'spline' : 'line', step: this._uzsudata.interpolation.type != 'cubic' && this._uzsudata.interpolation.type != 'linear' ? 'left' : false, }, false); @@ -2263,7 +2257,6 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { chart.redraw(); self._plotNowLine(); - // Highcharts.seriesTypes.scatter.prototype.getPointSpline = Highcharts.seriesTypes.line.prototype.getPointSpline; }, _save: function() { From 9b7758f00859b4d0bf372b2ba753b94b80358c43 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sun, 17 Dec 2023 21:42:49 +0100 Subject: [PATCH 004/112] Add docu page for user extensions --- pages/docu/index.html | 8 +++ pages/docu/misc/extensions.html | 112 ++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 pages/docu/misc/extensions.html diff --git a/pages/docu/index.html b/pages/docu/index.html index 77bb5584..fa036c9a 100755 --- a/pages/docu/index.html +++ b/pages/docu/index.html @@ -165,6 +165,13 @@

Fundamentals

Overview

+
  • + + {{ lib.svgimg ('', 'control_switch_m_4.svg', 'icon0', '') }} + +

    User Extensions

    +
    +
  • {{ lib.svgimg ('', 'jquery_gear.svg', 'icon0', '') }} @@ -272,6 +279,7 @@

    Design

    Misc

    Fundamentals  Overview  + User Extensions  Widget Assistant  Appliances  Replacing deprecated Widgets  diff --git a/pages/docu/misc/extensions.html b/pages/docu/misc/extensions.html new file mode 100644 index 00000000..e51c5061 --- /dev/null +++ b/pages/docu/misc/extensions.html @@ -0,0 +1,112 @@ +/** +* ----------------------------------------------------------------------------- +* @package smartVISU +* @author Martin Gleiß +* @copyright 2012 - 2023 +* @license GPL [http://www.gnu.de] +* ----------------------------------------------------------------------------- +*/ + + +{% extends "index.html" %} +{% block content %} + +

    User Extensions

    + +
    + + Depending on the update method used, smartVISU files can get overwritten during the update, so changes and extensions written by the user can get lost. Therefore it is highly recommended to not change any + of the original files and folders that come with smartVISU. There are special protected areas reserved for user-written code. + +

    Custom Pages

    + + Folders with indiviual visu pages shall be added to the folder ./pages. Such a folder is named ./pages/<yourPages> in the present docuemntation. All subfolders in ./pages not originating from smartVISU + sources will be protected from being overwritten during an update. Pages can also be added to the ./dropins folder but in favour of a clear page structure and origin this is not recommended. + +

    + +

    Dropins Folder for User generated Content

    + + All user generated code should be placed into the ./dropins folder and its subfolders which are protected, too. Generally, the ./dropins folders will be read at the very end of the page loading process. + This will result in different behaviour of duplicate filenames depending on the file type. + + Although there are namespaces and dedicated folder structures now for widgets and icons it is recommended to avoid using duplicate filenames in order to avoid unwanted behaviour. It is good practise + to name own extensions e.g. with a "my_" in front which is not used in all official smartVISU content. +

    + + +

    Custom Widgets

    + + Place your own widgets .html and .js files in ./dropins/widgets. They will be imported automatically from there. See custom widget docu for the generation + of your own documentation page for the widget. If you need to import certain widgets within your own widgets use the '@widgets' namespace: +
    + {% filter trim|escape|nl2br %}{% verbatim %} + {% import "@widgets/mywidget.html" as mywidget %} + {% endverbatim %}{% endfilter %} +
    + SmarthomeNG - if auto page generation is activated - places plugin widgets in ./dropins/shwidgets. Don't place your own widgets there since smarthomeNG will empty the folder on startup.
    + Widgets can also be placed in a subfolder of your pages folder: ./pages/<yourPages>/widgets. They will get imported from there but the docu page in the folder will not be found. +

    + +

    Additional Icons

    + + Place your own icons in ./dropins/icons. They will be found automatically there. Normally, white icons are sufficient and should be placed in ./dropins/icons/ws. smartVISU widgets will not use black icons + unless you force them to do so by specifying the full path. Black icons should then be placed in ./dropins/icons/sw.
    + Keep in mind that SVG icons need a certain format to enable dynamic colorization in smartVISU. The forum and also the GitHub wiki provide some hints for that. +

    + +

    Language Customization

    + + smartVISU loads the language information recursively, starting with the configured language file and continuing with the language in the "extends = xx" statement. Extensions of the language files + can be placed in the folder ./dropins/lang. The procedure is described in ./lang/readme.txt. Create a file named e.g. "custom.ini", define the base language and fill in your individual formats and texts. + +
    + {% filter trim|escape|nl2br %}{% verbatim %} + ;define a title or delete the following line + ;@label My Language + ;define the base language + extends = "en" + + ;do not forget the category names + [format] + kwh = "%01,3f kWh" + ... + {% endverbatim %}{% endfilter %} +
    + The custom language will be displayed in the configuration menu with its file name or - if the @label line is present - with the defined label. Select this item to activate the language extension. + +

    + +

    Custom CSS definitions

    + + There are many options to place custom CSS definitions for styling pages and widgets. + + +
    + +

    Javascript Extensions

    + + JavaScript extensions besides the .js widget files can be made in the following ways: + + Defining a JavaScript function or widget in a folder with high priority will replace the function with the same name read earlier in a low priority folder. +

    + +

    More Information

    + + More information can be found on the wiki pages.
    + Please contribute there if you like to share your knowledge. We'll integrate the contents into this inline documentation later. + +
    + +{% endblock %} From 3ac291852400b579f274dc8f57af386a18636ba1 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:22:33 +0100 Subject: [PATCH 005/112] improve spline display and clip to xmin / xmax boundaries --- changelog.md | 2 + pages/base/base.css | 11 +- widgets/device.js | 496 ++++++++++++++++++++++++-------------------- 3 files changed, 279 insertions(+), 230 deletions(-) diff --git a/changelog.md b/changelog.md index e15f3d16..f749ea9e 100755 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ### New / Changed Widgets ### Other New Features +- improved spline display for the starting point in device.uzsugraph ### Improvements @@ -14,6 +15,7 @@ ### Fixed Bugs - system page was not shown due to missing infoblock.html if pages were configured to "Smarthome" but pages had not been created yet by the "smartvisu" plugin of smarthomeNG. - behaviour of device.uzsugraph interpolation style was inconsistent if more than one uzsugraph widget was on a page. +- device.uzsugraph threw an error while a point was dragged ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/pages/base/base.css b/pages/base/base.css index 86885173..df979342 100644 --- a/pages/base/base.css +++ b/pages/base/base.css @@ -1609,7 +1609,10 @@ div.ui-slider-switch{ .uzsuCell [data-tip]:after { left: -190px; } -.highcharts-root .uzsu-event-sunrise { +/** + * ---------- UZSU Graph ------------------------------------------------ + */ + .highcharts-root .uzsu-event-sunrise { fill: #ffffff; stroke: rgba(255, 255, 255, 0.5); } @@ -1629,6 +1632,12 @@ div.ui-slider-switch{ fill: #808080; stroke: #808080; } +[data-widget="device.uzsugraph"] .highcharts-navigator, +[data-widget="device.uzsugraph"] .highcharts-navigator-series, +[data-widget="device.uzsugraph"] .highcharts-navigator-xaxis, +[data-widget="device.uzsugraph"] .highcharts-navigator-yaxis { + display: none; +} /** * --- W i d g e t s : M U L T I M E D I A ---------------------------------- diff --git a/widgets/device.js b/widgets/device.js index 99211076..36a77727 100755 --- a/widgets/device.js +++ b/widgets/device.js @@ -1215,7 +1215,7 @@ $.widget("sv.device_uzsu", $.sv.widget, { }, //---------------------------------------------------------------------------- - // Funktionen für das Sortrieren der Tabelleneinträge + // Funktionen für das Sortieren der Tabelleneinträge //---------------------------------------------------------------------------- _uzsuSortTime: function(response, e) { // erst aus dem Widget zurücklesen, sonst kann nicht im Array sortiert werden (alte daten) @@ -1742,11 +1742,10 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { Highcharts.VMLRenderer.prototype.symbols.sunset = Highcharts.SVGRenderer.prototype.symbols.sunset; } - // draw the plot + // prepare the plot var chart = this.element.highcharts({ title: { text: this.options.headline }, legend: false, - scrollbar: {enabled: this.options.scrollbar}, series: [ { // active name: 'active', @@ -1816,13 +1815,12 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { ], xAxis: { type: 'datetime', - min: this._startTimestamp, + ordinal:false, + min: this._startTimestamp, max: 1000*60*60*24 + this._startTimestamp, showLastLabel: false, crosshair: { snap: false }, - dateTimeLabelFormats: { - day: '%a' - } + dateTimeLabelFormats: { day: '%a' } }, yAxis: { title: false, @@ -1844,10 +1842,22 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { var value = (this.series.yAxis.categories) ? this.series.yAxis.categories[this.y] : this.y; return '' + value + ' (' + this.series.name + ')
    '; } - }, + navigator: { + enabled: true, + min: this._startTimestamp, + max: 1000*60*60*24*7 + this._startTimestamp, + }, + rangeSelector: { + enabled: false, + }, + scrollbar: { + enabled: this.options.scrollbar, + showFull: false + }, chart: { styledMode: true, + marginBottom: 0, exporting: { enabled: false }, @@ -1884,39 +1894,39 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { dragPrecisionY: step, dragMinY: min, dragMaxY: max - }, - cursor: this.options.editable ? 'move' : null, - marker: { enabled: true }, - stickyTracking: false, - findNearestPointBy: 'xy', - type: 'scatter', - lineWidth: 2, - point: { - events: { - click: function (e) { - if(self.justDragged) { // prevent click event after drop - self.justDragged = false; - return; - } - if(e.point.uzsuEntry !== undefined) - self._uzsuRuntimePopup(e.point.uzsuEntry) - }, - drag: function (e) { - }, - drop: function (e) { - self.justDragged = true; // used to prevent click event after drop - if(e.target.uzsuEntry !== undefined) { - e.target.uzsuEntry.value = e.target.y; - if(e.target.uzsuEntry.event == 'time') - e.target.uzsuEntry.timeCron = self.element.highcharts().time.dateFormat('%H:%M', e.target.x); - else // sunrise or sunset - e.target.uzsuEntry.timeOffset = Math.round(((e.target.x % (1000*60*60*24)) - (self._getSunTime(e.target.uzsuEntry.event, 0) % (1000*60*60*24)))/1000/60); - //uzsuEntry.active - self._save(); - } - } - } - } + }, + cursor: this.options.editable ? 'move' : null, + marker: { enabled: true }, + dataGrouping: { enabled: false }, + stickyTracking: false, + findNearestPointBy: 'xy', + type: 'scatter', + lineWidth: 2, + point: { + events: { + click: function (e) { + if(self.justDragged) { // prevent click event after drop + self.justDragged = false; + return; + } + if(e.point.uzsuEntry !== undefined) + self._uzsuRuntimePopup(e.point.uzsuEntry) + }, + drag: function (e) { + }, + drop: function (e) { + self.justDragged = true; // used to prevent click event after drop + if(e.target.uzsuEntry !== undefined) { + e.target.uzsuEntry.value = e.target.y; + if(e.target.uzsuEntry.event == 'time') + e.target.uzsuEntry.timeCron = self.element.highcharts().time.dateFormat('%H:%M', e.target.x); + else // sunrise or sunset + e.target.uzsuEntry.timeOffset = Math.round(((e.target.x % (1000*60*60*24)) - (self._getSunTime(e.target.uzsuEntry.event, 0) % (1000*60*60*24)))/1000/60); + self._save(); + } + } + } + } } }, }, @@ -2009,14 +2019,14 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { // Zoom buttons self.customZoomButtons = [ - { day: 'mo', shape: 'square', xMin: self._startTimestamp, xMax: self._startTimestamp + 24*60*60*1000 }, - { day: 'tu', shape: 'square', xMin: self._startTimestamp + 24*60*60*1000, xMax: self._startTimestamp + 2*24*60*60*1000 }, - { day: 'we', shape: 'square', xMin: self._startTimestamp + 24*60*60*1000, xMax: self._startTimestamp + 2*24*60*60*1000 }, - { day: 'th', shape: 'square', xMin: self._startTimestamp + 24*60*60*1000, xMax: self._startTimestamp + 2*24*60*60*1000 }, - { day: 'fr', shape: 'square', xMin: self._startTimestamp + 24*60*60*1000, xMax: self._startTimestamp + 2*24*60*60*1000 }, - { day: 'sa', shape: 'square', xMin: self._startTimestamp + 24*60*60*1000, xMax: self._startTimestamp + 2*24*60*60*1000 }, - { day: 'su', shape: 'square', xMin: self._startTimestamp + 24*60*60*1000, xMax: self._startTimestamp + 2*24*60*60*1000 }, - { day: 'w', shape: 'circle', langKey: 'week', xMin: self._startTimestamp, xMax: self._startTimestamp + 7 *24*60*60*1000 }, + { day: 'mo', shape: 'square', xMin: 0, xMax: 24*60*60*1000 }, + { day: 'tu', shape: 'square', xMin: 24*60*60*1000, xMax: 2*24*60*60*1000 }, + { day: 'we', shape: 'square', xMin: 2*24*60*60*1000, xMax: 3*24*60*60*1000 }, + { day: 'th', shape: 'square', xMin: 3*24*60*60*1000, xMax: 4*24*60*60*1000 }, + { day: 'fr', shape: 'square', xMin: 4*24*60*60*1000, xMax: 5*24*60*60*1000 }, + { day: 'sa', shape: 'square', xMin: 5*24*60*60*1000, xMax: 6*24*60*60*1000 }, + { day: 'su', shape: 'square', xMin: 6*24*60*60*1000, xMax: 7*24*60*60*1000 }, + { day: 'w', shape: 'circle', xMin: 0, xMax: 7 *24*60*60*1000, langKey: 'week' }, ]; chart.text = []; @@ -2032,7 +2042,7 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { button.element = chart.renderer.button('', null, null, function(e) { chart.xAxis[0].setExtremes(); - chart.xAxis[0].update({min: button.xMin, max: button.xMax}, false); + chart.xAxis[0].update({min: self._startTimestamp + button.xMin, max: self._startTimestamp + button.xMax}, false); chart.redraw(); }, {}, {}, {}, {}, button.shape) .attr({ @@ -2059,22 +2069,26 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { }, draw: function() { - var chart = this.element.highcharts(); + var chart = this.element.highcharts(); - if(this._uzsudata.active) - this.element.removeClass('uzsu-all-inactive'); - else - this.element.addClass('uzsu-all-inactive'); + if(this._uzsudata.active) + this.element.removeClass('uzsu-all-inactive'); + else + this.element.addClass('uzsu-all-inactive'); - var hasDays = false; - var hasSunrise = false; - var hasSunset = false; + var hasDays = false; + var hasSunrise = false; + var hasSunset = false; var hasBurst = false; - var seriesData = { active: [], inactive: [], range: [] }; - var linetype = this._uzsudata.interpolation.type == 'cubic' ? 'spline' : 'line'; - // Highcharts.seriesTypes.scatter.prototype.getPointSpline = Highcharts.seriesTypes[linetype].prototype.getPointSpline; - - // as from v3.2 we start with "today" instead of Monday + var seriesData = { active: [], inactive: [], range: [] }; + var linetype = this._uzsudata.interpolation.type == 'cubic' ? 'spline' : 'line'; + var self = this; + + + // ****************************************************************************** + // set start timestamp for the graph and texts for weekdays in zoom buttons - + // as from v3.2 the graph starts with current day instead of Monday + // ****************************************************************************** var today = new Date().getDay(); // delivers SU = 0 this._startTimestamp = (4 + today - 1) % 7 *1000*60*60*24 + new Date(0).getTimezoneOffset()*1000*60; @@ -2084,186 +2098,210 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { this.rruleDays[day] += 7; this.customZoomButtons[this.rruleDays[day]].day = day.toLowerCase(); } + $.each(this.customZoomButtons, function(idx, button) { + chart.text[idx].attr({ text: sv_lang.uzsu[button.day] }); + }); + + + // ****************************************************************************** + // highlight active interpolation button + // ****************************************************************************** + $.each(this.interpolationButtons, function(idx, button) { + if(button.interpolationType == self._uzsudata.interpolation.type) + button.element.addClass("icon1"); + else + button.element.removeClass("icon1"); + }); - var self = this; + // ****************************************************************************** + // create timeline with all events from UZSU data + // ****************************************************************************** $.each(this._uzsudata.list, function(responseEntryIdx, responseEntry) { - // in der Tabelle die Werte der rrule, dabei gehe ich von dem Standardformat FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU aus und setze für jeden Eintrag den Button. - var x, xMin, xMax, xSeriesEndMin, xSeriesEndMax; - var burstTimes = []; - hasBurst = responseEntry.hasOwnProperty('series'); - hasSunrise = hasSunrise || responseEntry.event == 'sunrise'; - hasSunset = hasSunset ||responseEntry.event == 'sunset'; - if(!hasBurst){ - x = self._calculateEventTime(responseEntry); + var x, xMin, xMax, xSeriesEndMin, xSeriesEndMax; + hasBurst = responseEntry.hasOwnProperty('series'); + hasSunrise = hasSunrise || responseEntry.event == 'sunrise'; + hasSunset = hasSunset ||responseEntry.event == 'sunset'; + if(!hasBurst){ + x = self._calculateEventTime(responseEntry); if(responseEntry.timeMin) xMin = self._timeToTimestamp(responseEntry.timeMin); if(responseEntry.timeMax) xMax = self._timeToTimestamp(responseEntry.timeMax); - } - else { - x = self._calculateEventTime(responseEntry.series.start); - if(responseEntry.series.start.timeMin) - xMin = self._timeToTimestamp(responseEntry.series.start.timeMin); - if(responseEntry.series.start.timeMax) - xMax = self._timeToTimestamp(responseEntry.series.start.timeMax); - - // determine uzsu series times - var seriesEnd; - var seriesIntervall = new Date('1970-01-01T' + responseEntry.series.timeSeriesIntervall + ':00Z').getTime(); - if (responseEntry.series.hasOwnProperty('timeSeriesCount')) // series end defined by count of cycles - seriesEnd = x + (parseInt(responseEntry.series.end.timeCron) - 1) * seriesIntervall; - else - seriesEnd = self._calculateEventTime(responseEntry.series.end); - if(responseEntry.series.end.timeMin) + } + else { + x = self._calculateEventTime(responseEntry.series.start); + if(responseEntry.series.start.timeMin) + xMin = self._timeToTimestamp(responseEntry.series.start.timeMin); + if(responseEntry.series.start.timeMax) + xMax = self._timeToTimestamp(responseEntry.series.start.timeMax); + + // determine uzsu series times + var seriesEnd; + var seriesIntervall = new Date('1970-01-01T' + responseEntry.series.timeSeriesIntervall + ':00Z').getTime(); + if (responseEntry.series.hasOwnProperty('timeSeriesCount')) // series end defined by count of cycles + seriesEnd = x + (parseInt(responseEntry.series.end.timeCron) - 1) * seriesIntervall; + else + seriesEnd = self._calculateEventTime(responseEntry.series.end); + if(responseEntry.series.end.timeMin) xSeriesEndMin = self._timeToTimestamp(responseEntry.series.end.timeMin); - if(responseEntry.series.end.timeMax) + if(responseEntry.series.end.timeMax) xSeriesEndMax = self._timeToTimestamp(responseEntry.series.end.timeMax); - } + } - var rrule = responseEntry.rrule; - if (!rrule) - rrule = 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU'; - var ind = rrule.indexOf('BYDAY='); - // if RRULE BYDAY is included - if (ind > 0) { - var days = rrule.substring(ind+6).split(','); - if (days.length < 7) - hasDays = true; - - // push all events into the timeline - - $.each(days, function(dayIdx, day) { - var rruleOffset = self.rruleDays[day]*1000*60*60*24; - var sunOffset = 0; - if (responseEntry.event.indexOf('sun') >= 0 ) - sunOffset = self._getSunTime(responseEntry.event, self.rruleDays[day]) - self._getSunTime(responseEntry.event, 0); - else if (hasBurst && responseEntry.series.start.event.indexOf('sun') >= 0 ) - sunOffset = self._getSunTime(responseEntry.series.start.event, self.rruleDays[day]) - self._getSunTime(responseEntry.series.start.event, 0); - sunOffset = (x + sunOffset < xMin ? xMin - x : sunOffset); - sunOffset = (x + sunOffset > xMax ? xMax - x : sunOffset); - - var xRecurring = x + rruleOffset + sunOffset; - var yValue = Number(responseEntry.value); - seriesData[responseEntry.active ? 'active' : 'inactive'].push({ x: xRecurring, y: yValue, className: 'uzsu-'+responseEntryIdx+' uzsu-event-'+responseEntry.event, entryIndex: responseEntryIdx, uzsuEntry: responseEntry }); - if (hasBurst){ - var sunSeriesEndOffset = 0; - if (responseEntry.series.end.event.indexOf('sun') >= 0 ) - sunSeriesEndOffset = self._getSunTime(responseEntry.series.end.event, self.rruleDays[day]) - self._getSunTime(responseEntry.series.end.event, 0); - sunSeriesEndOffset = (seriesEnd + sunSeriesEndOffset < xSeriesEndMin ? xSeriesEndMin - seriesEnd : sunSeriesEndOffset); - sunSeriesEndOffset = (seriesEnd + sunSeriesEndOffset > xSeriesEndMax ? xSeriesEndMax - seriesEnd : sunSeriesEndOffset); - - xBurstRecurring = xRecurring; - while (xBurstRecurring + seriesIntervall <= seriesEnd + rruleOffset + sunSeriesEndOffset) { - xBurstRecurring += seriesIntervall; - seriesData['active'].push({ x: xBurstRecurring, y: yValue, className: 'uzsu-'+responseEntryIdx+' uzsu-event-'+responseEntry.event, entryIndex: responseEntryIdx, uzsuEntry: responseEntry }); - } - } - if(!hasBurst && (xMin !== undefined || xMax !== undefined)) { - if(xMin !== undefined) - seriesData.range.push({ x: xMin+rruleOffset, y: yValue, name: sv_lang.uzsu.earliest, uzsuEntry: responseEntry, className: 'uzsu-min' }); - else - seriesData.range.push({ x: xRecurring, y: yValue, uzsuEntry: responseEntry, className: 'uzsu-min uzsu-hidden', marker: { enabled: false } }); - if(xMax !== undefined) - seriesData.range.push({ x: xMax+rruleOffset, y: yValue, name: sv_lang.uzsu.latest, uzsuEntry: responseEntry, className: 'uzsu-max' }); - else - seriesData.range.push({ x: xRecurring, y: yValue, uzsuEntry: responseEntry, className: 'uzsu-max uzsu-hidden', marker: { enabled: false } }); - seriesData.range.push({ x: xMax+rruleOffset+1, y: null, uzsuEntry: responseEntry }); - } - }); - } - }); + var rrule = responseEntry.rrule; + if (!rrule) + rrule = 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR,SA,SU'; + var ind = rrule.indexOf('BYDAY='); + // if RRULE BYDAY is included + if (ind > 0) { + var days = rrule.substring(ind+6).split(','); + // memorize whether at least one data point is not repeating every day so the initial display will be a week graph + if (days.length < 7) + hasDays = true; + + // push all events into the timeline + $.each(days, function(dayIdx, day) { + var rruleOffset = self.rruleDays[day]*1000*60*60*24; + var sunOffset = 0; + if (responseEntry.event.indexOf('sun') >= 0 ) + sunOffset = self._getSunTime(responseEntry.event, self.rruleDays[day]) - self._getSunTime(responseEntry.event, 0); + else if (hasBurst && responseEntry.series.start.event.indexOf('sun') >= 0 ) + sunOffset = self._getSunTime(responseEntry.series.start.event, self.rruleDays[day]) - self._getSunTime(responseEntry.series.start.event, 0); + sunOffset = (x + sunOffset < xMin ? xMin - x : sunOffset); + sunOffset = (x + sunOffset > xMax ? xMax - x : sunOffset); + + var xRecurring = x + rruleOffset + sunOffset; + var yValue = Number(responseEntry.value); + seriesData[responseEntry.active ? 'active' : 'inactive'].push({ x: xRecurring, y: yValue, className: 'uzsu-'+responseEntryIdx+' uzsu-event-'+responseEntry.event, entryIndex: responseEntryIdx, uzsuEntry: responseEntry }); + if (hasBurst){ + var sunSeriesEndOffset = 0; + if (responseEntry.series.end.event.indexOf('sun') >= 0 ) + sunSeriesEndOffset = self._getSunTime(responseEntry.series.end.event, self.rruleDays[day]) - self._getSunTime(responseEntry.series.end.event, 0); + sunSeriesEndOffset = (seriesEnd + sunSeriesEndOffset < xSeriesEndMin ? xSeriesEndMin - seriesEnd : sunSeriesEndOffset); + sunSeriesEndOffset = (seriesEnd + sunSeriesEndOffset > xSeriesEndMax ? xSeriesEndMax - seriesEnd : sunSeriesEndOffset); + + xBurstRecurring = xRecurring; + while (xBurstRecurring + seriesIntervall <= seriesEnd + rruleOffset + sunSeriesEndOffset) { + xBurstRecurring += seriesIntervall; + seriesData['active'].push({ x: xBurstRecurring, y: yValue, className: 'uzsu-'+responseEntryIdx+' uzsu-event-'+responseEntry.event, entryIndex: responseEntryIdx, uzsuEntry: responseEntry }); + } + } + if(!hasBurst && (xMin !== undefined || xMax !== undefined)) { + if(xMin !== undefined) + seriesData.range.push({ x: xMin+rruleOffset, y: yValue, name: sv_lang.uzsu.earliest, uzsuEntry: responseEntry, className: 'uzsu-min' }); + else + seriesData.range.push({ x: xRecurring, y: yValue, uzsuEntry: responseEntry, className: 'uzsu-min uzsu-hidden', marker: { enabled: false } }); + if(xMax !== undefined) + seriesData.range.push({ x: xMax+rruleOffset, y: yValue, name: sv_lang.uzsu.latest, uzsuEntry: responseEntry, className: 'uzsu-max' }); + else + seriesData.range.push({ x: xRecurring, y: yValue, uzsuEntry: responseEntry, className: 'uzsu-max uzsu-hidden', marker: { enabled: false } }); + seriesData.range.push({ x: xMax+rruleOffset+1, y: null, uzsuEntry: responseEntry }); + } + }); + } + }); - var xMax = 1000*60*60*24 * (hasDays ? 7 : 1) + this._startTimestamp; + var navigatorMin = this._startTimestamp + var navigatorMax = navigatorMin + 7*24*60*60*1000; - // active points - var data = seriesData.active; - data.sort(function(a,b) { return a.x - b.x }); + // ****************************************************************************** + // set plot data for active points + // ****************************************************************************** + var data = seriesData.active; + data.sort(function(a,b) { return a.x - b.x }); - // add graph points at the ends (not clickable since entryIndex and uzsuEntry are missing) - if(data.length > 0) { - data.unshift({ x: data[data.length-1].x-1000*60*60*24*7, y: data[data.length-1].y, className: data[data.length-1].className }); - data.push({ x: data[1].x+1000*60*60*24*7, y: data[1].y, className: data[1].className }); - } + // add graph points at the ends (not clickable since entryIndex and uzsuEntry are missing) + // 2 points at start in order to improve fitting of spline to UZSU interpolation - chart.get('active').setData(data, false, null, false); - chart.get('active').update({ - type: this._uzsudata.interpolation.type == 'cubic' ? 'spline' : 'line', - step: this._uzsudata.interpolation.type != 'cubic' && this._uzsudata.interpolation.type != 'linear' ? 'left' : false, - }, false); - - // inactive points - data = seriesData.inactive; - data.sort(function(a,b) { return a.x - b.x }); - chart.get('inactive').setData(data, false, null, false); - - // min/max times on sun events - chart.get('range').setData(seriesData.range, false, null, false); - - plotLines = []; - sunData = []; - if(hasSunrise || hasSunset) { - for(dayIdx = 0; dayIdx < 7; dayIdx++) { - if(hasSunrise) { - plotLines.push({ - value: self._getSunTime('sunrise', dayIdx)+dayIdx*1000*60*60*24, - className: 'uzsu-event-sunrise', - label: { text: sv_lang.uzsu.sunrise } - }); - sunData.push({ x: self._getSunTime('sunrise', dayIdx)+dayIdx*1000*60*60*24, y: chart.yAxis[0].min, name: sv_lang.uzsu.sunrise, className: 'uzsu-event-sunrise', uzsuEvent: 'sunrise', marker: { symbol: 'sunrise' } }); - } - if(hasSunset) { - plotLines.push({ - value: self._getSunTime('sunset', dayIdx)+dayIdx*1000*60*60*24, - className: 'uzsu-event-sunset', - label: { text: sv_lang.uzsu.sunset } - }); - sunData.push({ x: self._getSunTime('sunset', dayIdx)+dayIdx*1000*60*60*24, y: chart.yAxis[0].min, name: sv_lang.uzsu.sunset, className: 'uzsu-event-sunset', uzsuEvent: 'sunset', marker: { symbol: 'sunset' } }) - } - } - } - chart.xAxis[0].update({ - min: this._startTimestamp, - max: 1000*60*60*24 * (hasDays ? 7 : 1) + this._startTimestamp, - plotLines: plotLines - }, false); - chart.get('sun').setData(sunData, false); - - //set active interpolation button - $.each(this.interpolationButtons, function(idx, button) { - if(button.interpolationType == self._uzsudata.interpolation.type){ - button.element.addClass("icon1"); - // button.element.attr('style', 'cursor:pointer;'); - } - else { - button.element.removeClass("icon1"); - // button.element.attr('style', 'cursor:pointer;fill:transparent;'); - } - }); + if(data.length > 0) { + navigatorMin = data[data.length-1].x-1000*60*60*24*7; + data.unshift({ x: navigatorMin, y: data[data.length-1].y, className: data[data.length-1].className }); + data.unshift({ x: data[data.length-2].x-1000*60*60*24*7, y: data[data.length-2].y, className: data[data.length-2].className }); + navigatorMax = data[2].x+1000*60*60*24*7; + data.push({ x: navigatorMax, y: data[2].y, className: data[2].className }); + // 2nd point at end would cause highcharts to break the plot area boundaries during dragging -> omit this for now + // and wait for the solution for https://github.com/highcharts/highcharts/issues/20351 + // data.push({ x: data[3].x+1000*60*60*24*7, y: data[3].y, className: data[3].className }); + } - $.each(this.customZoomButtons, function(idx, button) { - if (idx < self.customZoomButtons.length -1){ - button.xMin = self._startTimestamp + idx * 1000*60*60*24; - button.xMax = button.xMin + 1000*60*60*24; - } else { - button.xMin = self._startTimestamp; - button.xMax = button.xMin + 7 * 1000*60*60*24; + chart.get('active').setData(data, false, null, false); + chart.get('active').update({ + type: this._uzsudata.interpolation.type == 'cubic' ? 'spline' : 'line', + step: this._uzsudata.interpolation.type != 'cubic' && this._uzsudata.interpolation.type != 'linear' ? 'left' : false, + }, false); + + + // ****************************************************************************** + // set plot data for inactive points + // ****************************************************************************** + data = seriesData.inactive; + data.sort(function(a,b) { return a.x - b.x }); + chart.get('inactive').setData(data, false, null, false); + + + // ****************************************************************************** + // set plot data for min/max times on sun events and the sun times themselves + // ****************************************************************************** + chart.get('range').setData(seriesData.range, false, null, false); + + plotLines = []; + sunData = []; + if(hasSunrise || hasSunset) { + for(dayIdx = 0; dayIdx < 7; dayIdx++) { + if(hasSunrise) { + plotLines.push({ + value: self._getSunTime('sunrise', dayIdx)+dayIdx*1000*60*60*24, + className: 'uzsu-event-sunrise', + label: { text: sv_lang.uzsu.sunrise } + }); + sunData.push({ x: self._getSunTime('sunrise', dayIdx)+dayIdx*1000*60*60*24, y: chart.yAxis[0].min, name: sv_lang.uzsu.sunrise, className: 'uzsu-event-sunrise', uzsuEvent: 'sunrise', marker: { symbol: 'sunrise' } }); + } + if(hasSunset) { + plotLines.push({ + value: self._getSunTime('sunset', dayIdx)+dayIdx*1000*60*60*24, + className: 'uzsu-event-sunset', + label: { text: sv_lang.uzsu.sunset } + }); + sunData.push({ x: self._getSunTime('sunset', dayIdx)+dayIdx*1000*60*60*24, y: chart.yAxis[0].min, name: sv_lang.uzsu.sunset, className: 'uzsu-event-sunset', uzsuEvent: 'sunset', marker: { symbol: 'sunset' } }) + } } - chart.text[idx].attr({ - text: sv_lang.uzsu[button.day], - }); - }); + } + chart.get('sun').setData(sunData, false); + + + // ****************************************************************************** + // update the plot boundaries and draw the plot + // ****************************************************************************** + chart.xAxis[0].update({ + min: this._startTimestamp, + max: 1000*60*60*24 * (hasDays ? 7 : 1) + this._startTimestamp, + // floor: this._startTimestamp, + plotLines: plotLines + }, false); + + chart.update({ + navigator: { + xAxis: { + min: navigatorMin, + max: navigatorMax, + } + } + }); + + chart.xAxis[0].setExtremes(this._startTimestamp, 1000*60*60*24 * (hasDays ? 7 : 1) + this._startTimestamp) - chart.redraw(); + chart.redraw(); - self._plotNowLine(); + self._plotNowLine(); }, _save: function() { this._uzsuCollapseTimestring(this._uzsudata); this._write(this._uzsudata); - if(this._uzsuParseAndCheckResponse(this._uzsudata)) - this.draw(); + // this causes a highcharts error and is not necessary since _update() will be called anyway after writing + // if(this._uzsuParseAndCheckResponse(this._uzsudata)) + // this.draw(); }, _timeToTimestamp: function(time) { @@ -2271,19 +2309,19 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { }, _getSunTime: function(event, dayIndex) { - if(!this.sunTimes){ - this.sunTimes = { 'sunrise': [], 'sunset': [] }; - for (var day in this.rruleDays){ + if(!this.sunTimes){ + this.sunTimes = { 'sunrise': [], 'sunset': [] }; + for (var day in this.rruleDays){ if (this._uzsudata.hasOwnProperty('SunCalculated')){ this.sunTimes.sunrise[this.rruleDays[day]] = this._uzsudata.SunCalculated.sunrise[day]; this.sunTimes.sunset[this.rruleDays[day]] = this._uzsudata.SunCalculated.sunset[day]; } else { - this.sunTimes.sunrise[this.rruleDays[day]] = (this._uzsudata.sunrise == undefined) ? '06:00' : this._uzsudata.sunrise; - this.sunTimes.sunset[this.rruleDays[day]] = (this._uzsudata.sunset == undefined) ? '19:30' : this._uzsudata.sunset; + this.sunTimes.sunrise[this.rruleDays[day]] = (this._uzsudata.sunrise == undefined) ? '06:00' : this._uzsudata.sunrise; + this.sunTimes.sunset[this.rruleDays[day]] = (this._uzsudata.sunset == undefined) ? '19:30' : this._uzsudata.sunset; } - } - } - return this._timeToTimestamp(this.sunTimes[event][dayIndex]); + } + } + return this._timeToTimestamp(this.sunTimes[event][dayIndex]); }, _calculateEventTime: function(timeEvent){ @@ -2314,7 +2352,7 @@ $.widget("sv.device_uzsugraph", $.sv.device_uzsu, { axis.removePlotLine('now'); axis.addPlotLine({ id: 'now', - value: (this._timeToTimestamp(String(new Date().getHours()).padStart(2,'0')+':'+String(new Date().getMinutes()).padStart(2,'0')) - this._startTimestamp + 1000*60*60*24*((new Date().getDay() + 6) % 7)) % (axis.max - this._startTimestamp) + this._startTimestamp, + value: (this._timeToTimestamp(String(new Date().getHours()).padStart(2,'0')+':'+String(new Date().getMinutes()).padStart(2,'0')) - this._startTimestamp) + this._startTimestamp, className: 'uzsu-now highcharts-color-0', label: 'now' }); From 5c399ccf11457048b0217966b76df7ecf6431d86 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:30:36 +0100 Subject: [PATCH 006/112] remove template search path deprecated in v3.3 --- changelog.md | 1 + index.php | 27 +++++++++++++-------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/changelog.md b/changelog.md index f749ea9e..df7c9415 100755 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ ### Deprecated ### Removed Features +- old wiget pathnames w/o namespace (deprecated in v3.3) ### Fixed Bugs - system page was not shown due to missing infoblock.html if pages were configured to "Smarthome" but pages had not been created yet by the "smartvisu" plugin of smarthomeNG. diff --git a/index.php b/index.php index 661ffe91..b8ebc65e 100644 --- a/index.php +++ b/index.php @@ -2,8 +2,8 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Martin Gleiss - * @copyright 2012 - 2015 + * @author Martin Gleiß + * @copyright 2012 - 2023 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -44,39 +44,39 @@ // init template engine require_once const_path.'vendor/autoload.php'; + //---------------------------------------------------------- + // create search path for all templates + //---------------------------------------------------------- $loader = new \Twig\Loader\FilesystemLoader(const_path.'apps'); if (is_dir(const_path.'pages/'.$actual_pages)) $loader->addPath(const_path.'pages/'.$actual_pages); - if (is_dir(const_path.'pages/'.$actual_pages.'/widgets')) - $loader->addPath(const_path.'pages/'.$actual_pages.'/widgets'); // deprecated as of v3.3 - remove in a later version - if (dirname($request['page']) != '.' && is_dir(const_path.'pages/'.$actual_pages.'/'.dirname($request['page']))) $loader->addPath(const_path.'pages/'.$actual_pages.'/'.dirname($request['page'])); - // add smarthome dir if it is not directly chosen. - // allows combination of custom pages with auto-generated pages from smarthomeNG + // add smarthome dir if it is not directly chosen - allows combination of custom pages with auto-generated pages from smarthomeNG if (substr(config_driver, 0, 9) == 'smarthome' and $actual_pages != 'smarthome' and is_dir(const_path."pages/smarthome")) $loader->addPath(const_path.'pages/smarthome'); - // make sure SV doesn't load stuff from dropins unless pages are configured + // make sure SV doesn't load stuff from dropins unless pages are configured if ($actual_pages != '') { $loader->addPath(const_path.'dropins'); - $loader->addPath(const_path.'dropins/widgets'); // deprecated as of v3.3 - remove in a later version - $loader->addPath(const_path.'dropins/shwidgets'); // deprecated as of v3.3 - remove in a later version } $loader->addPath(const_path.'pages/base'); - $loader->addPath(const_path.'widgets'); // deprecated as of v3.3 - remove in a later version + //---------------------------------------------------------- // create widgets path in namespace @widgets + //---------------------------------------------------------- $loader->addPath(const_path.'widgets', 'widgets'); $loader->addPath(const_path.'dropins/widgets', 'widgets'); $loader->addPath(const_path.'dropins/shwidgets', 'widgets'); if (is_dir(const_path.'pages/'.$actual_pages.'/widgets')) $loader->addPath(const_path.'pages/'.$actual_pages.'/widgets', 'widgets'); + //---------------------------------------------------------- // create icons path in namespace @icons + //---------------------------------------------------------- $loader->addPath(const_path.'icons/ws', 'icons'); $loader->addPath(const_path.'dropins/icons/ws', 'icons'); @@ -112,8 +112,7 @@ $twig->addGlobal($key, $val); } - // TO DO: better global concept for "config_pages" in order to distinguish between actual pages and configured pages - // today global value in php can be different from value with same name in Twig if the "pages" parameter is set + // attention: global value for config_pages in php can be different from value with same name in Twig if the "pages" parameter is set $twig->addGlobal('config_pages', $actual_pages); $twig->addGlobal('configured_pages', config_pages); $twig->addGlobal('pagepath', dirname($request['page'])); @@ -187,7 +186,7 @@ echo str_repeat(" ", 60).date('H:i, d.m').", v".config_version_full."\n"; echo str_repeat("-", 80)."\n\n"; echo "Error loading Page '".$request['page']."' !\n\n"; - echo "Check config.php -> 'config_pages' for correct Pages/Project configuration\n"; + echo "Check configuration page for correct pages / project configuration\n"; echo "or try the index page!\n\n"; echo str_repeat("-", 80)."\n\n"; echo "\n"; From d98e70d1a4cfafb5e751a4440c648175da3d6a08 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:36:03 +0100 Subject: [PATCH 007/112] specify allowed database modes in the individual drivers --- changelog.md | 1 + driver/_io_template.js | 7 +++++++ driver/io_eibd.js | 15 ++++++--------- driver/io_fhem.js | 6 ++++++ driver/io_iobroker.js | 13 +++++++++++++ driver/io_json.js | 14 ++++++-------- driver/io_linknx.js | 15 +++++++-------- driver/io_offline.js | 6 ++++++ driver/io_openhab.js | 9 ++++++++- driver/io_smarthomeng.js | 7 +++++++ lib/base/base.js | 2 +- 11 files changed, 68 insertions(+), 27 deletions(-) diff --git a/changelog.md b/changelog.md index df7c9415..8f78ff79 100755 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ - improved spline display for the starting point in device.uzsugraph ### Improvements +- allowed database modes for series moved into the individual backend drivers ### Updated Libraries diff --git a/driver/_io_template.js b/driver/_io_template.js index 7346c2bd..b1fd259b 100644 --- a/driver/_io_template.js +++ b/driver/_io_template.js @@ -81,6 +81,13 @@ var io = { // only be called from the public functions above. You may add or delete some // to fit your requirements and your connected system. + + /** + * supported aggregate functions in the backends database + * TODO: check which aggregates are valid for the backend and adapt the array + */ + aggregates: ['avg', 'min', 'max', 'sum', 'diff', 'rate', 'on', 'raw', 'count'], + // TODO /** diff --git a/driver/io_eibd.js b/driver/io_eibd.js index fd676016..7dc05b2c 100644 --- a/driver/io_eibd.js +++ b/driver/io_eibd.js @@ -110,6 +110,11 @@ var io = { lastRequestStarted: 0, reloadAllDataTime: 1800, restartRequestTime: 60, + + /** + * supported aggregate functions in the backends database + */ + aggregates: [], checkRequest: function () { @@ -374,14 +379,6 @@ var io = { requestItem = itemArray[0] + '/' + itemArray[1] + '/' + itemArray[2]; return requestItem; - }, - - /** - * stop all subscribed series - */ - stopseries: function () { - // TODO - $.noop; } - + }; diff --git a/driver/io_fhem.js b/driver/io_fhem.js index ef9d6405..3219e10a 100755 --- a/driver/io_fhem.js +++ b/driver/io_fhem.js @@ -153,6 +153,12 @@ var io = { addon: null, socketErrorNotification: null, + /** + * supported aggregate functions in the backends database + * modes implemented in the fronthem file "99_fronthemUtils.pm" + */ + aggregates: ['avg', 'min', 'max', 'sum', 'raw'], + log: function(level, text) { if (io.logLevel >= level) { console.log("[io.fhem]: " + text); diff --git a/driver/io_iobroker.js b/driver/io_iobroker.js index 6208b64c..6c088a8e 100644 --- a/driver/io_iobroker.js +++ b/driver/io_iobroker.js @@ -192,6 +192,19 @@ var io = { }, listeners: [], + + /** + * supported aggregate functions in the backends database + * https://www.iobroker.net/docu/index-83.htm?page_id=4531&lang=en + * - minmax - is ordered as 2 series: min and max + * - min + * - max + * - avergage = avg + * - total = on + * - count + */ + aggregates: ['avg', 'min', 'max', 'on', 'count'], + /** * Opens the connection and add some handlers diff --git a/driver/io_json.js b/driver/io_json.js index dc34e0a8..0c21dd7f 100644 --- a/driver/io_json.js +++ b/driver/io_json.js @@ -104,6 +104,12 @@ var io = { timer: 0, timer_run: false, errorNotification: null, + + /** + * supported aggregate functions in the backends database + */ + aggregates: [], + /** * The real-time polling loop, only if there are listeners @@ -221,12 +227,4 @@ var io = { } }, - /** - * stop all subscribed series - */ - stopseries: function () { - // TODO - $.noop; - } - }; diff --git a/driver/io_linknx.js b/driver/io_linknx.js index 44e415bf..640bbfa2 100644 --- a/driver/io_linknx.js +++ b/driver/io_linknx.js @@ -103,6 +103,13 @@ var io = { timer: 0, timer_run: false, + + /** + * supported aggregate functions in the backends database + * TODO: check which aggregates are valid for ioBroker + */ + aggregates: [], + /** * The real-time polling loop, only if there are listeners @@ -202,12 +209,4 @@ var io = { } }, - /** - * stop all subscribed series - */ - stopseries: function () { - // TODO - $.noop; - } - }; diff --git a/driver/io_offline.js b/driver/io_offline.js index 89a44d3e..869bc4cd 100644 --- a/driver/io_offline.js +++ b/driver/io_offline.js @@ -125,6 +125,12 @@ var io = { seriesTimer: [], listeners: [], + /** + * supported aggregate functions in the backends database + */ + aggregates: ['avg', 'min', 'max', 'sum', 'diff', 'rate', 'on', 'raw', 'count'], + + /** * The real-time polling loop, only if there are listeners */ diff --git a/driver/io_openhab.js b/driver/io_openhab.js index a30542c9..24e9136c 100644 --- a/driver/io_openhab.js +++ b/driver/io_openhab.js @@ -4,7 +4,7 @@ * @author Martin Gleiß, Patrik Germann * @copyright 2012 - 2022 * @license GPL [http://www.gnu.de] - * @version 2.5.1 + * @version 2.5.2 * ----------------------------------------------------------------------------- * @label openHAB * @@ -189,6 +189,13 @@ var io = { auth : false, serverTimeout : 10, serverResponseTime : 0, + + /** + * supported aggregate functions in the backends database + * since this must be configured by the user in the backend we leave some common modes as dummies + */ + aggregates: ['avg', 'min', 'max', 'sum', 'diff', 'on', 'raw', 'count'], + /** * convert states from openHAB diff --git a/driver/io_smarthomeng.js b/driver/io_smarthomeng.js index 9647e970..b1118fc7 100644 --- a/driver/io_smarthomeng.js +++ b/driver/io_smarthomeng.js @@ -135,6 +135,13 @@ var io = { */ version: 4, shngProto: null, + + /** + * supported aggregate functions in the backends database + * https://smarthomeng.github.io/smarthome/plugins/database/README.html + * TODO: check how comparator for count can be implemented, e.g. "count>10" + */ + aggregates: ['avg', 'min', 'max', 'sum', 'on', 'raw', 'count', 'countall', 'integrate', 'differentiate'], /** * This is the websocket module / plugin and the websocket opening time diff --git a/lib/base/base.js b/lib/base/base.js index 741bd02a..13fe1ded 100755 --- a/lib/base/base.js +++ b/lib/base/base.js @@ -1476,7 +1476,7 @@ var widget = { aggregate = aggregate.substr(5); if(aggregate != undefined && aggregate.length > 5 && aggregate.substr(0,5) == 'count') aggregate = aggregate.substr(0,5); - return ($.inArray(aggregate, Array('avg', 'min', 'max', 'sum', 'diff', 'rate', 'on', 'raw', 'count')) >= 0); + return ($.inArray(aggregate, io.aggregates) >= 0); }, /** From 9296e38ada7c5f7444ca531ad89de8c24e679ed4 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sun, 7 Jan 2024 14:03:22 +0100 Subject: [PATCH 008/112] database mode check for templatechecker --- changelog.md | 1 + driver/io_smarthomeng.js | 2 +- lib/templatechecker/class.Config.php | 14 ++++++++++ lib/templatechecker/class.TemplateChecker.php | 15 +++++++++++ .../class.WidgetParameterChecker.php | 26 +++++++++++++++++-- readme.md | 2 +- widgets/plot.html | 4 +-- 7 files changed, 58 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 8f78ff79..f5371d6d 100755 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ ### Improvements - allowed database modes for series moved into the individual backend drivers +- parameter type "mode" introduced. Templatechecker reads available database modes individually for configured backend. ### Updated Libraries diff --git a/driver/io_smarthomeng.js b/driver/io_smarthomeng.js index b1118fc7..0f0b2be9 100644 --- a/driver/io_smarthomeng.js +++ b/driver/io_smarthomeng.js @@ -141,7 +141,7 @@ var io = { * https://smarthomeng.github.io/smarthome/plugins/database/README.html * TODO: check how comparator for count can be implemented, e.g. "count>10" */ - aggregates: ['avg', 'min', 'max', 'sum', 'on', 'raw', 'count', 'countall', 'integrate', 'differentiate'], + aggregates: ['avg', 'min', 'max', 'diff', 'sum', 'on', 'raw', 'count', 'countall', 'integrate', 'differentiate', 'duration'], /** * This is the websocket module / plugin and the websocket opening time diff --git a/lib/templatechecker/class.Config.php b/lib/templatechecker/class.Config.php index dfe32474..50a78567 100644 --- a/lib/templatechecker/class.Config.php +++ b/lib/templatechecker/class.Config.php @@ -66,5 +66,19 @@ class TemplateCheckerConfig { * of plot parameter sets which must be given */ const ArrayDimensionSetter = array ('quad.stateswitch' => 2, 'quad.select' => 2, 'quad.symbol' => 5, 'quad.print' => 1 ); + + /** + * Array of aggregation modes provided for plots by the individual backend systems + */ + const aggregationModes = array( + 'eibd' => array(), + 'fhem' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'sum', 'raw'), + 'iobroker' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'on', 'count'), + 'json' => array(), + 'linknx' => array(), + 'offline' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'sum', 'diff', 'rate', 'on', 'raw', 'count'), + 'openhab' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'sum', 'diff', 'on', 'raw', 'count'), + 'smarthomeng' => array('avg', 'min', 'max', 'minmax', 'minmaxavg', 'diff', 'sum', 'on', 'raw', 'count', 'countall', 'integrate', 'differentiate', 'duration') + ); } \ No newline at end of file diff --git a/lib/templatechecker/class.TemplateChecker.php b/lib/templatechecker/class.TemplateChecker.php index 644e6e09..6c9e57c8 100644 --- a/lib/templatechecker/class.TemplateChecker.php +++ b/lib/templatechecker/class.TemplateChecker.php @@ -30,6 +30,8 @@ class TemplateChecker { * @var type */ private $fileName; + + private $driver; /** * DOMDocument object for file @@ -41,6 +43,14 @@ class TemplateChecker { * Object for the Icons */ private $items; + + /** + * get configured driver + * @return string + */ + public function getDriver() { + return $this->driver; + } /** * Perform template checks for single file @@ -74,6 +84,11 @@ public function __construct($fileName, MessageCollection $messages, $checkItems) $this->items = new Items(pathinfo($fileName)["dirname"]); //new if ($checkItems == "false") { $this->items->setState(FALSE);} + + // some checks require info of the configured driver (e.g. database aggregation modes in backend) + // get the configured driver if templaechecker is called from the configured pages or use offline driver instead + $fileFolder = str_replace('/widgets', '', $widgetFolder); + $this->driver = (substr($fileFolder, 6, ) == config_pages ? config_driver : 'offline'); } /** diff --git a/lib/templatechecker/class.WidgetParameterChecker.php b/lib/templatechecker/class.WidgetParameterChecker.php index 9f0b406b..fd5b3468 100644 --- a/lib/templatechecker/class.WidgetParameterChecker.php +++ b/lib/templatechecker/class.WidgetParameterChecker.php @@ -119,7 +119,7 @@ public static function performChecks($widget, $paramIndex, $paramConfig, $messag * @param array $paramConfig Config for parameter to check * @param MessageCollection $messages Collection of messages to add messages to */ - private function __construct($widget, $paramIndex, $paramConfig, $messages,$items, $templateCecker) { + private function __construct($widget, $paramIndex, $paramConfig, $messages, $items, $templateCecker) { $this->widget = $widget; $this->paramIndex = $paramIndex; $this->paramConfig = $paramConfig; @@ -165,7 +165,7 @@ private function run() { $uzsuitem = $this->widget->getParam($this->paramIndex - 1); if ($uzsuitem == null || $uzsuitem == '') return; - // we can check only one of the items as dummy in the recursive uzsu eidget check + // we can check only one of the items as dummy in the recursive uzsu widget check // so item names in the info lines may differ from the real items // items have been checked as individual parameters before if (is_array($uzsuitem)){ @@ -200,6 +200,9 @@ private function run() { case 'item': $this->checkParameterTypeItem($value); //new break; + case 'mode': + $this->checkParameterTypeMode($value); //new + break; case 'type': $this->checkParameterTypeType($value); break; @@ -499,6 +502,25 @@ private function checkParameterTypeType($value) { $this->addError('WIDGET TYPE PARAM CHECK', 'Unknown type', $value); } + + /** + * Check widget parameter of type "mode" (database aggregations provided by the backend) + * @param $value mixed parameter value + */ + private function checkParameterTypeMode($value) { + if (!$this->checkParameterNotEmpty($value)) + return; + + $driver = $this->templateCecker->getDriver(); + if (in_array($value, TemplateCheckerConfig::aggregationModes[$driver])) + return; + + // additional widget-specific valid values + if ($this->checkParameterValidValues($value)) + return; + + $this->addError('WIDGET MODE PARAM CHECK', 'Mode not supported by backend "'.$driver.'"', $value); + } /** * Check widget parameter of type "color" diff --git a/readme.md b/readme.md index dd197458..a1a37162 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ SEE: [smartvisu.de](http://www.smartvisu.de) ## SYSTEM REQUIREMENTS * IP-Network, KNX-Bus * [smarthomeNG](https://github.com/smarthomeNG), [linknx](http://sourceforge.net/projects/linknx/), [ioBroker](https://github.com/ioBroker/ioBroker), [openHAB](https://www.openhab.org/), [FHEM](https://fhem.de/) or [knxd](https://github.com/knxd/knxd) (deprecated: [eibd](http://www.auto.tuwien.ac.at/~mkoegler/index.php/eibd) ) backend or JSON interface - * Webserver with PHP 7.3.2 and above. Compatibility with php v8.0 is verified. PHP packages required: libawl-php php-curl php php-json php-xml php-mbstring + * Webserver with PHP 7.3.2 and above. Compatibility with php v8.2 is verified. PHP packages required: libawl-php php-curl php php-json php-xml php-mbstring * Firefox, Chrome, IE, Safari, iPhone, iPad, Android Phone or Android Tablet diff --git a/widgets/plot.html b/widgets/plot.html index 05011536..20106fc0 100644 --- a/widgets/plot.html +++ b/widgets/plot.html @@ -74,7 +74,7 @@ * * @param {id=} unique id for this widget. Add the string "plotpopup" to activate on-demand plot data loading, i.e. plot data are not loaded during page creation but when the popup containing the plot is opened. (optional) * @param {item[?](bool,num,list)} series of item(s); multiple items in array form: [ item1 , item2 ] -* @param {text[?](avg,sum,min,max,minmax,minmaxavg,raw,on)=avg} the aggregation mode (optional, default 'avg') +* @param {mode[?]=avg} the aggregation mode (optional, default 'avg') May be a single mode or an array with separate mode per item. Available modes depend on backend, common ones are 'avg', 'sum', 'min', 'max', 'minmax', 'minmaxavg', 'on', 'raw'. See backend's documentation for further information. * @param {duration=1h} the minimum time (x-axis): '1h', '2h'... (duration-format) Value is interpreted as timestamp if no unit is given (e.g. 1629109298731). @@ -234,7 +234,7 @@ * * @param {id=} unique id for this widget (optional) * @param {item(bool,num,list)} series of item(s); multiple items in array form: [ item1 , item2 ] -* @param {text(avg,sum,min,max,raw,on)=avg} the aggregation mode (optional, default 'avg') +* @param {mode[?]=avg} the aggregation mode (optional, default 'avg') Available modes depend on backend, common ones are 'avg', 'sum', 'min', 'max', 'minmax', 'minmaxavg', 'on', 'raw'. See backend's documentation for further information. * @param {duration=1h} the minimum time (x-axis): '1h', '2h'... (duration-format) Value is interpreted as timestamp if no unit is given (e.g. 1629109298731). * @param {duration=now} the maximum time (x-axis): '', '1h', '2h'... (duration-format, default: now). Value is interpreted as timestamp if no unit is given (e.g. 1629109298731). From 1c30329d1f1726d40269be169d5e8d82d5cef829 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sun, 7 Jan 2024 19:10:16 +0100 Subject: [PATCH 009/112] fix status.activelist w/ default png icon --- changelog.md | 1 + pages/base/base.css | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index f5371d6d..838b44f6 100755 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,7 @@ - system page was not shown due to missing infoblock.html if pages were configured to "Smarthome" but pages had not been created yet by the "smartvisu" plugin of smarthomeNG. - behaviour of device.uzsugraph interpolation style was inconsistent if more than one uzsugraph widget was on a page. - device.uzsugraph threw an error while a point was dragged +- status.activelist did not display texts if no svg icon was selected, i.e. the default icon "trans.png" was displayed ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/pages/base/base.css b/pages/base/base.css index df979342..b7c4554c 100644 --- a/pages/base/base.css +++ b/pages/base/base.css @@ -250,7 +250,8 @@ a:hover { .ui-listview .ui-li-has-thumb > svg:first-child, .ui-listview .ui-li-has-thumb>.ui-btn>svg:first-child, -.ui-listview .ui-btn>svg.icon +.ui-listview .ui-btn>svg.icon, +.ui-listview .ui-btn>img.icon { position: absolute; left: 0; From 579ba800e5d1247b82681b5dadca30cd511467ed Mon Sep 17 00:00:00 2001 From: pacifica15 Date: Mon, 8 Jan 2024 11:39:30 +0100 Subject: [PATCH 010/112] Extended basic.tank enabling it to be colored depending on the value --- pages/docu/basic/widget_basic.tank.html | 6 ++++-- widgets/basic.html | 19 +++++++++++-------- widgets/basic.js | 23 ++++++++++++++++++----- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/pages/docu/basic/widget_basic.tank.html b/pages/docu/basic/widget_basic.tank.html index 5d19a29e..63f64794 100644 --- a/pages/docu/basic/widget_basic.tank.html +++ b/pages/docu/basic/widget_basic.tank.html @@ -22,7 +22,7 @@
    Examples
    {{ basic.tank('tank4', 'bath.value', 0, 255, 5, 'water') }} {{ basic.tank('tank5', 'bath.value', 0, 255, 5, 'water', '#0c0') }} {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pallets') }} - + {{ basic.tank('tank7', 'bath.value', 0, 255, 5, 'cylinder', ['#c00','#cc0','#0c0','#cc0','#c00'], [80,100,155,200]) }} {% endverbatim %}{% endfilter %}
    @@ -32,7 +32,8 @@
    Examples
    {{ basic.slider('slider1', 'bath.value', 0, 255, 5) }} - From left to right: 'none', 'cylinder', 'cylinder' orange, 'water', 'water' green, 'pallets' + From left to right: 'none', 'cylinder', 'cylinder' orange, 'water', 'water' green, 'pallets', 'cylinder'
    + The last one will have different colors depending on the value <80: red, 80-99: yellow, 100-154: green, 155-199: yellow, >=200: red
    {{ basic.tank('tank1', 'bath.value') }} @@ -41,6 +42,7 @@
    Examples
    {{ basic.tank('tank4', 'bath.value', 0, 255, 5, 'water') }} {{ basic.tank('tank5', 'bath.value', 0, 255, 5, 'water', '#0c0') }} {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pallets') }} + {{ basic.tank('tank7', 'bath.value', 0, 255, 5, 'cylinder', ['#c00','#cc0','#0c0','#cc0','#c00'], [80,100,155,200]) }}
    diff --git a/widgets/basic.html b/widgets/basic.html index 5ea3058f..c48c000b 100755 --- a/widgets/basic.html +++ b/widgets/basic.html @@ -618,7 +618,7 @@ * If more than one item is given, they will be combined with formula. * * @param {id=} unique id for this widget (optional) -* @param {item[?](bool,num,list,scene)=} zero, one or more item(s). More items in array form: [item1, item2] (optional) +* @param {item[?](bool,num,str,list,scene)=} zero, one or more item(s). More items in array form: [item1, item2] (optional) * @param {text[?]=} the text, printed when item has value val (optional) * @param {image[?]=control_on_off} the icon shown when item has value val (optional, default 'control_on_off' if text is empty) dynamic icons can be used, e.g. icon.light('', '', value_item); please note: these must not be wrapped by apostrophs (') @@ -684,23 +684,26 @@ * @param {value=255} the value for full (optional, default 255) * @param {value=5} step between two values (optional, default 5, for future use) * @param {text(none,cylinder,water,pallets)=none} the mode: 'none', 'cylinder', 'water', 'pallets' (optional, default 'none') -* @param {color=grey} the color of the filling e. g. '#f00' for red (optional, default grey) +* @param {color[?]=} array of colors; e. g. '#f00' for red (optional) +* @param {text[]=} array of upper thresholds; the color according to greatest reached threshold is applied (optional) */ -{% macro tank(id, item_pos, min, max, step, mode, color) %} -
     
    + {% endmacro %} diff --git a/widgets/basic.js b/widgets/basic.js index 5d66313f..29eac3fe 100644 --- a/widgets/basic.js +++ b/widgets/basic.js @@ -495,7 +495,7 @@ $.widget("sv.basic_color_slider", $.sv.basic_color, { var diffCount = oldColors == null ? 3 : (values[0] != oldColors[0]) + (values[1] != oldColors[1]) + (values[2] != oldColors[2]) ; this._lockfor = (colormodel == 'hsv' ? 0 : diffCount -1); // lock widget to ignore next 2 updates - if(diffCount > 0) { + if(diffCount >= 0) { var items = String(this.options.item).explode(); if(items[1] == '') { // all values as list in one item io.write(items[0], values); @@ -1376,11 +1376,13 @@ $.widget("sv.basic_symbol", $.sv.widget, { // ----- basic.tank ----------------------------------------------------------- $.widget("sv.basic_tank", $.sv.widget, { - initSelector: 'div[data-widget="basic.tank"]', + initSelector: 'div[data-widget="basic.tank2"]', options: { min: 0, - max: 255, + max: 255, + thresholds: "", + colors: "" }, _update: function(response) { @@ -1388,13 +1390,24 @@ $.widget("sv.basic_tank", $.sv.widget, { var min = this.options.min; var factor = Math.min(Math.max((response[0] - min) / (max - min), 0), 1); - + + // colorize + var value=parseFloat(response[0]) + var currentIndex = 0; + $.each(String(this.options.thresholds).explode(), function(index, threshold) { + if((isNaN(value) || isNaN(threshold)) ? (threshold > value) : (parseFloat(threshold) > parseFloat(value))) + return false; + currentIndex++; + }); + var color = String(this.options.colors).explode()[currentIndex]; + this.element.find('div').css('background-color', color); + this.element.attr('title', Math.round(factor * 100) + '%') .find('div').css('height', factor * this.element.height()); + }, }); - // ----- basic.trigger --------------------------------------------------------- $.widget("sv.basic_trigger", $.sv.widget, { From 42b29cb3edd48ac9f5cb636fa6958bcc10d8acb2 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:16:04 +0100 Subject: [PATCH 011/112] small changes on basic.tank - revert unwanted/undocumented collateral changes - define color as array in twig - fix old typo "pallets" --- changelog.md | 2 ++ .../{scale_pallets.png => scale_pellets.png} | Bin pages/docu/basic/widget_basic.tank.html | 6 +++--- widgets/basic.html | 17 +++++++++-------- widgets/basic.js | 4 ++-- 5 files changed, 16 insertions(+), 13 deletions(-) rename pages/base/pics/{scale_pallets.png => scale_pellets.png} (100%) diff --git a/changelog.md b/changelog.md index 838b44f6..50127462 100755 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,6 @@ ## 3.4.a ### New / Changed Widgets +- basic.tank is now able to change colors according to reached thresholds ### Other New Features - improved spline display for the starting point in device.uzsugraph @@ -20,6 +21,7 @@ - behaviour of device.uzsugraph interpolation style was inconsistent if more than one uzsugraph widget was on a page. - device.uzsugraph threw an error while a point was dragged - status.activelist did not display texts if no svg icon was selected, i.e. the default icon "trans.png" was displayed +- corrected background image name "scale_pallets.png" to "scale_pellets.png" ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/pages/base/pics/scale_pallets.png b/pages/base/pics/scale_pellets.png similarity index 100% rename from pages/base/pics/scale_pallets.png rename to pages/base/pics/scale_pellets.png diff --git a/pages/docu/basic/widget_basic.tank.html b/pages/docu/basic/widget_basic.tank.html index 63f64794..f5f9c402 100644 --- a/pages/docu/basic/widget_basic.tank.html +++ b/pages/docu/basic/widget_basic.tank.html @@ -21,7 +21,7 @@
    Examples
    {{ basic.tank('tank3', 'bath.value', 0, 255, 5, 'cylinder', '#f90' ) }} {{ basic.tank('tank4', 'bath.value', 0, 255, 5, 'water') }} {{ basic.tank('tank5', 'bath.value', 0, 255, 5, 'water', '#0c0') }} - {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pallets') }} + {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pellets') }} {{ basic.tank('tank7', 'bath.value', 0, 255, 5, 'cylinder', ['#c00','#cc0','#0c0','#cc0','#c00'], [80,100,155,200]) }} {% endverbatim %}{% endfilter %} @@ -32,7 +32,7 @@
    Examples
    {{ basic.slider('slider1', 'bath.value', 0, 255, 5) }} - From left to right: 'none', 'cylinder', 'cylinder' orange, 'water', 'water' green, 'pallets', 'cylinder'
    + From left to right: 'none', 'cylinder', 'cylinder' orange, 'water', 'water' green, 'pellets', 'cylinder'
    The last one will have different colors depending on the value <80: red, 80-99: yellow, 100-154: green, 155-199: yellow, >=200: red
    @@ -41,7 +41,7 @@
    Examples
    {{ basic.tank('tank3', 'bath.value', 0, 255, 5, 'cylinder', '#f90' ) }} {{ basic.tank('tank4', 'bath.value', 0, 255, 5, 'water') }} {{ basic.tank('tank5', 'bath.value', 0, 255, 5, 'water', '#0c0') }} - {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pallets') }} + {{ basic.tank('tank6', 'bath.value', 0, 255, 5, 'pellets') }} {{ basic.tank('tank7', 'bath.value', 0, 255, 5, 'cylinder', ['#c00','#cc0','#0c0','#cc0','#c00'], [80,100,155,200]) }}
    diff --git a/widgets/basic.html b/widgets/basic.html index c48c000b..6f80504b 100755 --- a/widgets/basic.html +++ b/widgets/basic.html @@ -618,7 +618,7 @@ * If more than one item is given, they will be combined with formula. * * @param {id=} unique id for this widget (optional) -* @param {item[?](bool,num,str,list,scene)=} zero, one or more item(s). More items in array form: [item1, item2] (optional) +* @param {item[?](bool,num,list,scene)=} zero, one or more item(s). More items in array form: [item1, item2] (optional) * @param {text[?]=} the text, printed when item has value val (optional) * @param {image[?]=control_on_off} the icon shown when item has value val (optional, default 'control_on_off' if text is empty) dynamic icons can be used, e.g. icon.light('', '', value_item); please note: these must not be wrapped by apostrophs (') @@ -683,22 +683,23 @@ * @param {value=0} the value for empty (optional, default 0) * @param {value=255} the value for full (optional, default 255) * @param {value=5} step between two values (optional, default 5, for future use) -* @param {text(none,cylinder,water,pallets)=none} the mode: 'none', 'cylinder', 'water', 'pallets' (optional, default 'none') -* @param {color[?]=} array of colors; e. g. '#f00' for red (optional) -* @param {text[]=} array of upper thresholds; the color according to greatest reached threshold is applied (optional) +* @param {text(none,cylinder,water,pellets)=none} the mode: 'none', 'cylinder', 'water', 'pellets' (optional, default 'none') +* @param {color[?]=} single color or array of colors; e. g. '#f00' for red. If thresholds are used provide one color more than thresholds (optional) +* @param {text[]=} array of upper thresholds; the color according to highest reached threshold is applied (optional) */ {% macro tank(id, item_pos, min, max, step, mode, color, threshold) %} -
     
    diff --git a/widgets/basic.js b/widgets/basic.js index 29eac3fe..8704a6e4 100644 --- a/widgets/basic.js +++ b/widgets/basic.js @@ -495,7 +495,7 @@ $.widget("sv.basic_color_slider", $.sv.basic_color, { var diffCount = oldColors == null ? 3 : (values[0] != oldColors[0]) + (values[1] != oldColors[1]) + (values[2] != oldColors[2]) ; this._lockfor = (colormodel == 'hsv' ? 0 : diffCount -1); // lock widget to ignore next 2 updates - if(diffCount >= 0) { + if(diffCount > 0) { var items = String(this.options.item).explode(); if(items[1] == '') { // all values as list in one item io.write(items[0], values); @@ -1376,7 +1376,7 @@ $.widget("sv.basic_symbol", $.sv.widget, { // ----- basic.tank ----------------------------------------------------------- $.widget("sv.basic_tank", $.sv.widget, { - initSelector: 'div[data-widget="basic.tank2"]', + initSelector: 'div[data-widget="basic.tank"]', options: { min: 0, From a0299806017a66f11fb4db87988230443cab5978 Mon Sep 17 00:00:00 2001 From: pacifica15 Date: Thu, 11 Jan 2024 08:37:04 +0100 Subject: [PATCH 012/112] Fixed a bug in basic.print printing UNIX timestamps in milliseconds --- widgets/basic.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/widgets/basic.js b/widgets/basic.js index 8704a6e4..4a959c02 100644 --- a/widgets/basic.js +++ b/widgets/basic.js @@ -795,8 +795,14 @@ $.widget("sv.basic_print", $.sv.widget, { var value; // value for threshold comparison if (formatLower == 'date' || formatLower == 'time' || formatLower == 'short' || formatLower == 'long') { // Date - value = new Date(calc); - calc = value.transUnit(format); + if (isNaN(calc)) { + theDate = new Date(calc); + } + else { + theDate = new Date(parseFloat(calc)); + } + calc = theDate.transUnit(format); + value = theDate.getTime(); } else if (formatLower == 'script') { // Script value = null; From 1d4f061bf4942bbf904fe6611903439771aea647 Mon Sep 17 00:00:00 2001 From: pacifica15 Date: Thu, 11 Jan 2024 13:13:43 +0100 Subject: [PATCH 013/112] Created a new icon to send an automatic vacuum cleaner back to its docking station --- icons/sw/scene_robo_vac_cleaner_dock.svg | 23 +++++++++++++++++++++++ icons/ws/scene_robo_vac_cleaner_dock.svg | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 icons/sw/scene_robo_vac_cleaner_dock.svg create mode 100644 icons/ws/scene_robo_vac_cleaner_dock.svg diff --git a/icons/sw/scene_robo_vac_cleaner_dock.svg b/icons/sw/scene_robo_vac_cleaner_dock.svg new file mode 100644 index 00000000..ab1d0cb8 --- /dev/null +++ b/icons/sw/scene_robo_vac_cleaner_dock.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/ws/scene_robo_vac_cleaner_dock.svg b/icons/ws/scene_robo_vac_cleaner_dock.svg new file mode 100644 index 00000000..23520cee --- /dev/null +++ b/icons/ws/scene_robo_vac_cleaner_dock.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + From 5988b8a1c3beb923244ac10217046ed665648670 Mon Sep 17 00:00:00 2001 From: pacifica15 Date: Thu, 11 Jan 2024 13:15:34 +0100 Subject: [PATCH 014/112] Created a new icon for a combined scene "cooking and drinking" to be used with KNX scenes. --- icons/sw/scene_cooking_drink.svg | 27 +++++++++++++++++++++++++++ icons/ws/scene_cooking_drink.svg | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 icons/sw/scene_cooking_drink.svg create mode 100644 icons/ws/scene_cooking_drink.svg diff --git a/icons/sw/scene_cooking_drink.svg b/icons/sw/scene_cooking_drink.svg new file mode 100644 index 00000000..9e496acb --- /dev/null +++ b/icons/sw/scene_cooking_drink.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/ws/scene_cooking_drink.svg b/icons/ws/scene_cooking_drink.svg new file mode 100644 index 00000000..45de0dc5 --- /dev/null +++ b/icons/ws/scene_cooking_drink.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + From 79fd4d194b0ed9850286b78f55eb69a801463ddc Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:31:57 +0100 Subject: [PATCH 015/112] fix basic.print timedate output --- changelog.md | 2 ++ widgets/basic.html | 8 ++++---- widgets/basic.js | 11 +++-------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index 50127462..4a8af2ea 100755 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ ### Other New Features - improved spline display for the starting point in device.uzsugraph +- new icons scene_cooking_drink and scene_robovac_dock (thanks to @Pacifia15)) ### Improvements - allowed database modes for series moved into the individual backend drivers @@ -22,6 +23,7 @@ - device.uzsugraph threw an error while a point was dragged - status.activelist did not display texts if no svg icon was selected, i.e. the default icon "trans.png" was displayed - corrected background image name "scale_pallets.png" to "scale_pellets.png" +- basic.print did not print timestamps correctly as dates and was not able to colorize them ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/widgets/basic.html b/widgets/basic.html index 6f80504b..4231b1c4 100755 --- a/widgets/basic.html +++ b/widgets/basic.html @@ -333,7 +333,7 @@ * @param {formula=VAR} any valid JavaScript expression with following variables and aggregate functions (optional, default: VAR) - VAR1, VAR2, ... represent the corresponding item's value, VAR is an array of all item values - SUM(VAR), AVG(VAR), SUB(VAR), MIN(VAR) and MAX(VAR) aggregate the values -* @param {text[]=} array of upper thresholds; the color according to greatest reached threshold is applied (optional) +* @param {text[]=} array of upper thresholds; the color according to highest reached threshold is applied (optional) * @param {color[?](icon0to5,hidden,blank)=icon0} array of colors; 'icon0' through 'icon5' or e. g. '#f00' for red (optional, default = icon0 ) additionally you can use 'hidden' to not diplay at all or 'blank' to make it invisible but preserve the space that would be used. the first one is the base color for values below first threshold, so pass one color more than thresholds. @@ -349,7 +349,7 @@ {% set color = color|default('icon0') %} {% if href is empty %}{% else %}{% endif %} --- {% if href is empty %}{% else %}{% endif %} @@ -625,10 +625,10 @@ * @param {text[?]=1} comparative value(s) (default 1) * @param {formula=or} 'or', 'and' or any JavaScript expression with following variables, result will be compared to comparative value above. Additionally you can use 'min' and 'max' to test if any of the defined items has a value >= or <= comparative value (default 'or') VAR1, VAR2, ... represent the corresponding item's value, VAR is an array of all item values - If the formula starts with a greater than symbol '>' the value will be treated as threshold. The symbol (text, icon and color) according to greatest reached threshold will be shown. The first symbol is the base for values below first threshold, so pass one symbol more than values. + If the formula starts with a greater than symbol '>' the value will be treated as threshold. The symbol (text, icon and color) according to highest reached threshold will be shown. The first symbol is the base for values below first threshold, so pass one symbol more than values. If '>' is not used, symbols will only be shown if the item value matches exactly one of the given value parameters * @param {color[?](icon0to5)=icon0} the color 'icon1' or e. g. '#f00' for red (default 'icon0' of the design) - this only applies to text, if no icon is set (for backward compatibility) + this applies to text only if no icon is set (for backward compatibility) * @param {text=} URL to use as link (optional) * @param {text=} used in combination with href as data-rel attribute {e.g. to open a popup} (optional) * @param {type(btn-micro,btn-mini,btn-midi)=icon} icon type with or without button. valid types: 'micro','mini','midi','icon', 'btn-micro', 'btn-mini', 'btn-midi' (optional, default = 'icon') diff --git a/widgets/basic.js b/widgets/basic.js index 4a959c02..25f40823 100644 --- a/widgets/basic.js +++ b/widgets/basic.js @@ -795,14 +795,9 @@ $.widget("sv.basic_print", $.sv.widget, { var value; // value for threshold comparison if (formatLower == 'date' || formatLower == 'time' || formatLower == 'short' || formatLower == 'long') { // Date - if (isNaN(calc)) { - theDate = new Date(calc); - } - else { - theDate = new Date(parseFloat(calc)); - } - calc = theDate.transUnit(format); - value = theDate.getTime(); + value = new Date(calc); + calc = value.transUnit(format); + value = value.getTime(); } else if (formatLower == 'script') { // Script value = null; From a15dfccfe3349fee262ac4b0d779f202f12aff6f Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sat, 13 Jan 2024 20:47:22 +0100 Subject: [PATCH 016/112] show icons in dropins/icons as well in docu --- changelog.md | 1 + pages/docu/design/design_icons.html | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 4a8af2ea..b3c792c9 100755 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ ### Improvements - allowed database modes for series moved into the individual backend drivers - parameter type "mode" introduced. Templatechecker reads available database modes individually for configured backend. +- docu page design>icons shows all icons in dropins/icons/ws (or .../sw, whatever is configured) ### Updated Libraries diff --git a/pages/docu/design/design_icons.html b/pages/docu/design/design_icons.html index e6bc13e7..416b68e8 100644 --- a/pages/docu/design/design_icons.html +++ b/pages/docu/design/design_icons.html @@ -191,7 +191,7 @@
    Examples
    Icons located in "{{ icon0 }}" -
    Important: These images are available as .svg-image only. By default smartVisu does not come with .png-images. +
    Important: These images are available as .svg-image only. By default smartVISU does not come with .png-images.
    {% set cat = 'au' %} @@ -205,6 +205,19 @@
    Examples
    {{ file.name }}
    {% endfor %} +


    + +
    + Custom SVG icons provided by you in "dropins/{{ icon0 }}" +
    + {% set iconlist = dir('dropins/'~icon0, '(.*\.svg)') %} + {% for file in iconlist %} + + {% endfor %} + - - {% if export == 2 %} - - {% endif %} - {% endif %} - {% set source = source|default('database') %} - {% set tmax = (tmax|default('now')|lower=='0h' or tmax==0 or tmax is empty) ? 'now' : tmax %} - {% set tmin = tmin|default('1h') %} - - {% set mode = (mode is iterable ? mode : [mode|default('avg')]) %} - {% set count = (count is iterable ? count : [count|default(100)]) %} - {% set unit = (unit is iterable ? unit : [unit|default('')]) %} - {% set units = [] %} - {% for uniti in unit %} - {% set units = units|merge([uniti|replace({',' : ';'})]) %} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ - {% endfor %} - {% set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) %} - {% set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) %} - {% set item = (item is iterable ? item : [item]) %} - {% set seriesitems = [] %} - {% for itemi in item %} - {% if mode|length < loop.index %} - {% set mode = mode|merge([mode|last]) %} - {% endif %} - {% set modei = mode[loop.index0] %} - - {% if count|length < loop.index %} - {% set count = count|merge([count|last]) %} - {% endif %} - {% set counti = count[loop.index0] %} - - {% if source == 'database' %} - {% if modei == 'minmax' %} - {% set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]) ]) %} - {% elseif modei == 'minmaxavg' %} - {% set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]), implode(itemi, ['avg', tmin, tmax, counti]) ]) %} - {% else %} - {% set seriesitems = seriesitems|merge([implode(itemi, [modei, tmin, tmax, counti])]) %} - {% endif %} - {% else %} - {% set seriesitems = seriesitems|merge([itemi]) %} - {% endif %} - {% endfor %} + {%- if (not export is empty) and export > 0 -%} + {%- if once('plotimageexport') -%} + + + {%- endif -%} + {%- if export == 2 -%} + {%- if once('plotdataexport') -%} + + {%- endif -%} + {%- endif -%} + {%- endif -%} + + {%- set source = source|default('database') -%} + {%- set tmax = (tmax|default('now')|lower=='0h' or tmax==0 or tmax is empty) ? 'now' : tmax -%} + {%- set tmin = tmin|default('1h') -%} + + {%- set mode = (mode is iterable ? mode : [mode|default('avg')]) -%} + {%- set count = (count is iterable ? count : [count|default(100)]) -%} + {%- set unit = (unit is iterable ? unit : [unit|default('')]) -%} + {%- set units = [] -%} + {%- for uniti in unit -%} + {%- set units = units|merge([uniti|replace({',' : ';'})]) -%} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ + {%- endfor -%} + {%- set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) -%} + {%- set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) -%} + {%- set item = (item is iterable ? item : [item]) -%} + {%- set seriesitems = [] -%} + {%- for itemi in item -%} + {%- if mode|length < loop.index -%} + {%- set mode = mode|merge([mode|last]) -%} + {%- endif -%} + {%- set modei = mode[loop.index0] -%} + + {%- if count|length < loop.index -%} + {%- set count = count|merge([count|last]) -%} + {%- endif -%} + {%- set counti = count[loop.index0] -%} + {%- if mode == 'raw' or counti >= 5000 -%} + {%- if once('plotboost') -%} + + {%- endif -%} + {%- endif -%} + {%- if source == 'database' -%} + {%- if modei == 'minmax' -%} + {%- set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]) ]) -%} + {%- elseif modei == 'minmaxavg' -%} + {%- set seriesitems = seriesitems|merge([ implode(itemi, ['min', tmin, tmax, counti]), implode(itemi, ['max', tmin, tmax, counti]), implode(itemi, ['avg', tmin, tmax, counti]) ]) -%} + {%- else -%} + {%- set seriesitems = seriesitems|merge([implode(itemi, [modei, tmin, tmax, counti])]) -%} + {%- endif -%} + {%- else -%} + {%- set seriesitems = seriesitems|merge([itemi]) -%} + {%- endif -%} + {%- endfor -%} + class="plot{% if zoom == 'advanced' %} plot-highstock{% endif %}"> + {% endmacro %} @@ -258,6 +268,12 @@ {% set tmin = tmin|default('1h') %} {% set mode = mode|default('avg') %} {% set count = count|default(100) %} + {%- if mode == 'raw' or count >= 5000 -%} + {%- if once('plotboost') -%} + + {%- endif -%} + {%- endif -%} + /** there is no way to measure the series length from here like in plot.period. So we can't import boost.js on demand for big series. Load it explicitely on the visu page if needed. */ {% set width = width|default(120) %} {% set height = height|default(20) %} {% set unit = unit|default('') %} @@ -378,20 +394,24 @@ */ {% macro xyplot(id, item, xitem, yitem, xmin, xmax, ymin, ymax, label, xylabel, color, exposure, axis, zoom, assign, opposite, ycolor, ytype, unit, chartoptions, stacking, stacks, export) %} - {% if (not export is empty) and export > 0 %} - - - {% if export == 2 %} - - {% endif %} - {% endif %} - {% set unit = (unit is iterable ? unit : [unit|default('')]) %} - {% set units = [] %} - {% for uniti in unit %} - {% set units = units|merge([uniti|replace({',' : ';'})]) %} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ - {% endfor %} - {% set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) %} - {% set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) %} + {%- if (not export is empty) and export > 0 -%} + {%- if once('plotimageexport') -%} + + + {%- endif -%} + {%- if export == 2 -%} + {%- if once('plotdataexport') -%} + + {%- endif -%} + {%- endif -%} + {%- endif -%} + {%- set unit = (unit is iterable ? unit : [unit|default('')]) -%} + {%- set units = [] -%} + {%- for uniti in unit -%} + {%- set units = units|merge([uniti|replace({',' : ';'})]) -%} /** hide comma before implode/explode to allow format strings, e.g. %01,02f% */ + {%- endfor -%} + {%- set stacking = (stacking is iterable ? stacking : [stacking|default('normal')]) -%} + {%- set stacks = (stacks is iterable ? stacks : [stacks|default('0')]) -%} = 5000 && chart.series[seriesIndex].options.dataGrouping.enabled && chart.options.boost.enabled) + chart.series[seriesIndex].update({"dataGrouping": {"enabled": false}}, false); } //DEBUG: console.log('chart redraw _update() end'); chart.redraw(); @@ -1690,11 +1702,11 @@ $.widget("sv.plot_xyplot", $.sv.widget, { var exportmenu = (this.options.exportmenu >= 1); var styles = []; - // series - var series = []; - var seriesCount = this.items.length; + // series + var series = []; + var seriesCount = this.items.length; - for (var i = 0; i < seriesCount; i++) { + for (var i = 0; i < seriesCount; i++) { var stack = (stacks.length-1 >= i ? stacks[i]: stacks[stacks.length-1]); var stackingMode = (stacking[stack] ? stacking[stack] : stacking[0]); series.push({ @@ -1706,7 +1718,8 @@ $.widget("sv.plot_xyplot", $.sv.widget, { showInNavigator: true, stacking: (exposure[i] != null && exposure[i].toLowerCase().endsWith('stack') ? stackingMode : null), stack: (exposure[i] != null && exposure[i].toLowerCase().endsWith('stack') ? stack : null), - borderRadius: 0 + borderRadius: 0, + dataGrouping: {enabled: (exposure[i] != null && exposure[i].toLowerCase().endsWith('stair') ? false : true)}, }); } @@ -1754,25 +1767,33 @@ $.widget("sv.plot_xyplot", $.sv.widget, { max: xMax, }, stickToMax: false - }, + }, rangeSelector:{ enabled: false}, - yAxis: yaxis, - legend: { - enabled: label.length > 0, - align: 'center', - verticalAlign: 'top', - floating: true, - }, - tooltip: { - shared: true, - split: false, - pointFormatter: function() { - var unit = this.series.yAxis.userOptions.svUnit; - var value = (this.series.yAxis.categories) ? this.series.yAxis.categories[this.y] : parseFloat(this.y).transUnit(unit); - return '\u25CF ' + this.series.name + ': ' + value + '
    '; - } - }, - navigation: { // options for export context menu + yAxis: yaxis, + legend: { + enabled: label.length > 0, + align: 'center', + verticalAlign: 'top', + floating: true, + }, + boost: { + debug: { + timeRendering:false, + timeSeriesProcessing:false, + timeSetup:false + }, + enabled:true + }, + tooltip: { + shared: true, + split: false, + pointFormatter: function() { + var unit = this.series.yAxis.userOptions.svUnit; + var value = (this.series.yAxis.categories) ? this.series.yAxis.categories[this.y] : parseFloat(this.y).transUnit(unit); + return '\u25CF ' + this.series.name + ': ' + value + '
    '; + } + }, + navigation: { // options for export context menu buttonOptions: { enabled: exportmenu, height: 22, @@ -1851,8 +1872,12 @@ $.widget("sv.plot_xyplot", $.sv.widget, { var itemCount = response.length; for (var i = 0; i < itemCount; i++) { - if (response[i]) - chart.series[i].setData(response[i], false); + if (response[i]){ + chart.series[i].setData(response[i], false); + // disable data grouping for series with 5000 points and more if boost mode is enabled in order to let boost mode work + if (response[i].length >= 5000 && chart.series[i].options.dataGrouping.enabled && chart.options.boost.enabled) + chart.series[i].update({"dataGrouping": {"enabled": false}}, false); + } } chart.redraw(); }, From 964a6432b35755d7864fcbfa6bec8fdabe177a62 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Fri, 8 Mar 2024 08:33:50 +0100 Subject: [PATCH 039/112] swap min/max temp in forecast --- lib/weather/service/open-meteo.com.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/weather/service/open-meteo.com.php b/lib/weather/service/open-meteo.com.php index bb4b94bc..ca56c41f 100644 --- a/lib/weather/service/open-meteo.com.php +++ b/lib/weather/service/open-meteo.com.php @@ -112,7 +112,7 @@ public function run() $this->data['forecast'][$i]['date'] = $day; $this->data['forecast'][$i]['conditions'] = translate(WMO::weatherCode[$parsed_json->{'daily'}->{'weather_code'}[$index]]['condition'], 'met.no'); $this->data['forecast'][$i]['icon'] = WMO::weatherCode[$parsed_json->{'daily'}->{'weather_code'}[$index]]['icon']; - $this->data['forecast'][$i]['temp'] = round((float)$parsed_json->{'daily'}->{'temperature_2m_min'}[$index], 0) . '°/' . round((float)$parsed_json->{'daily'}->{'temperature_2m_max'}[$index], 0) . '°'; + $this->data['forecast'][$i]['temp'] = round((float)$parsed_json->{'daily'}->{'temperature_2m_max'}[$index], 0) . '°/' . round((float)$parsed_json->{'daily'}->{'temperature_2m_min'}[$index], 0) . '°'; $i++; } From a69607bb110d59d71482414596f32bc13c87ca66 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Mon, 18 Mar 2024 18:27:30 +0100 Subject: [PATCH 040/112] check twig syntax of html pages --- changelog.md | 1 + lib/templatechecker/class.TemplateChecker.php | 55 +++++++++++++++++++ lib/templatechecker/templatechecker.php | 4 +- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index b642df41..ddb4700c 100755 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ - parameter type "mode" introduced. Templatechecker reads available database modes individually for configured backend. - docu page design>icons shows all icons in dropins/icons/ws (or .../sw, whatever is configured) - Highcharts module boost.js is activated on demand for series with 5000 or more data points to speed up rendering (plot.period, plot.sparkline). In plot.xyplot we can not do this since there is no size info. But most likely list items with xy-data are not so big. +- templatechecker now checks the twig syntax of html pages ### Updated Libraries diff --git a/lib/templatechecker/class.TemplateChecker.php b/lib/templatechecker/class.TemplateChecker.php index 6c9e57c8..8379c983 100644 --- a/lib/templatechecker/class.TemplateChecker.php +++ b/lib/templatechecker/class.TemplateChecker.php @@ -121,12 +121,62 @@ public function performTests() { return; } + if (!$this->checkTwigTemplate($absFile)) + return; + if (!$this->readFile($absFile)) return; $this->checkNode($this->domDocument->documentElement); } + private function checkTwigTemplate($absFile) { + $templatePath = pathinfo($absFile, PATHINFO_DIRNAME); + $loader = new \Twig\Loader\FilesystemLoader($templatePath); + $twig = new \Twig\Environment($loader); + $twig->addFilter( new \Twig\TwigFilter('_', 'twig_concat')); + $twig->addFilter( new \Twig\TwigFilter('bit', 'twig_bit')); + $twig->addFilter( new \Twig\TwigFilter('substr', 'twig_substr')); + $twig->addFilter( new \Twig\TwigFilter('smartdate', 'twig_smartdate')); + $twig->addFilter( new \Twig\TwigFilter('deficon', 'twig_deficon', array('needs_environment' => true))); + $twig->addFilter( new \Twig\TwigFilter('md5', 'twig_md5')); + $twig->addFilter( new \Twig\TwigFilter('preg_replace', 'twig_preg_replace')); + + $twig->addFunction( new \Twig\TwigFunction('uid', 'twig_uid')); + $twig->addFunction( new \Twig\TwigFunction('once', 'twig_once')); + $twig->addFunction( new \Twig\TwigFunction('isfile', 'twig_isfile')); + $twig->addFunction( new \Twig\TwigFunction('isdir', 'twig_isdir')); + $twig->addFunction( new \Twig\TwigFunction('dir', 'twig_dir')); + $twig->addFunction( new \Twig\TwigFunction('docu', 'twig_docu')); + $twig->addFunction( new \Twig\TwigFunction('configmeta', 'twig_configmeta')); + $twig->addFunction( new \Twig\TwigFunction('lang', 'twig_lang')); + $twig->addFunction( new \Twig\TwigFunction('read_config', 'twig_read_config')); + $twig->addFunction( new \Twig\TwigFunction('timezones', 'twig_timezones')); + $twig->addFunction( new \Twig\TwigFunction('implode', 'twig_implode', array('is_safe' => array('html')))); + $twig->addFunction( new \Twig\TwigFunction('items', 'twig_items')); + $twig->addFunction( new \Twig\TwigFunction('asset_exists', 'twig_asset_exists')); + $twig->addFunction( new \Twig\TwigFunction('localize_svg', 'twig_localize_svg')); + + // init lexer comments + $lexer = new \Twig\Lexer($twig, array('tag_comment' => array('/**', '*/'))); + $twig->setLexer($lexer); + $templateName = pathinfo($absFile, PATHINFO_BASENAME); + + try { + // Lade das Template + $template = $twig->load($templateName); + + // Prüfe die Syntax des Templates + $template->getSourceContext()->getCode(); + + //if (Settings::SHOW_SUCCESS_TOO) + // $this->messages->addInfo('TWIG PARSER', 'Twig syntax is valid.'); + } catch (\Twig\Error\SyntaxError $e) { + $this->messages->addError('TWIG TEMPLATE PARSER', 'Twig syntax error: '. $e->getMessage(), $e->getLine()); + } + return true; + } + /** * Open file * @return \DOMDocument @@ -244,8 +294,13 @@ public function checkWidget($node, $macro) { return; $widgetName = $widget->getName(); + + // strip calling macros name in recursive check if (strpos($widgetName, '->') > 0 ) $widgetName = substr($widgetName, strpos($widgetName, '->') + 2); + + // a test with twig tokenize as marcro syntax checker in this place showed no significant advantage + // https://stackoverflow.com/questions/27191916/check-if-string-has-valid-twig-syntax $widgetConfig = $this->getWidgetConfig($widgetName); diff --git a/lib/templatechecker/templatechecker.php b/lib/templatechecker/templatechecker.php index 363ad563..95dd49c6 100644 --- a/lib/templatechecker/templatechecker.php +++ b/lib/templatechecker/templatechecker.php @@ -4,7 +4,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Thomas Ernst - * @copyright 2016 + * @copyright 2016 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -19,7 +19,7 @@ require_once 'class.Items.php'; require_once 'class.OldWidgets.php'; require_once 'class.itemProperties.php'; - +require_once const_path.'vendor/autoload.php'; RequestHandler::run(); From 8ea179ae01ae08a99224d58aa375ad4ad211f84c Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sat, 23 Mar 2024 19:36:53 +0100 Subject: [PATCH 041/112] deactivate console.log suppression in debug mode --- assets.php | 2 +- changelog.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/assets.php b/assets.php index 9012ee09..8898ce25 100644 --- a/assets.php +++ b/assets.php @@ -28,7 +28,7 @@ require_once $path . '/path-converter/src/ConverterInterface.php'; require_once $path . '/path-converter/src/Converter.php'; -if($type == 'javascript') +if($type == 'javascript' && !config_debug) array_unshift($request['files'], 'console.log = function() {};'); foreach($request['files'] as $fileName) { diff --git a/changelog.md b/changelog.md index ddb4700c..86118cf0 100755 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ - docu page design>icons shows all icons in dropins/icons/ws (or .../sw, whatever is configured) - Highcharts module boost.js is activated on demand for series with 5000 or more data points to speed up rendering (plot.period, plot.sparkline). In plot.xyplot we can not do this since there is no size info. But most likely list items with xy-data are not so big. - templatechecker now checks the twig syntax of html pages +- console.log is activated in cache mode if parameter "debug" is set to "1" in config.ini ### Updated Libraries From 04465b344d1f7f75f014566facbda7f180568685 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sat, 23 Mar 2024 19:48:50 +0100 Subject: [PATCH 042/112] Revert "deactivate console.log suppression in debug mode" This reverts commit 8ea179ae01ae08a99224d58aa375ad4ad211f84c. --- assets.php | 2 +- changelog.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/assets.php b/assets.php index 8898ce25..9012ee09 100644 --- a/assets.php +++ b/assets.php @@ -28,7 +28,7 @@ require_once $path . '/path-converter/src/ConverterInterface.php'; require_once $path . '/path-converter/src/Converter.php'; -if($type == 'javascript' && !config_debug) +if($type == 'javascript') array_unshift($request['files'], 'console.log = function() {};'); foreach($request['files'] as $fileName) { diff --git a/changelog.md b/changelog.md index 86118cf0..ddb4700c 100755 --- a/changelog.md +++ b/changelog.md @@ -16,7 +16,6 @@ - docu page design>icons shows all icons in dropins/icons/ws (or .../sw, whatever is configured) - Highcharts module boost.js is activated on demand for series with 5000 or more data points to speed up rendering (plot.period, plot.sparkline). In plot.xyplot we can not do this since there is no size info. But most likely list items with xy-data are not so big. - templatechecker now checks the twig syntax of html pages -- console.log is activated in cache mode if parameter "debug" is set to "1" in config.ini ### Updated Libraries From bfaca2a82dc447fe4856b59b76abf1fb7a1fefa8 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sat, 23 Mar 2024 19:51:40 +0100 Subject: [PATCH 043/112] deactivate console.log() suppression in debug mode --- assets.php | 2 +- changelog.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/assets.php b/assets.php index 9012ee09..a9188491 100644 --- a/assets.php +++ b/assets.php @@ -28,7 +28,7 @@ require_once $path . '/path-converter/src/ConverterInterface.php'; require_once $path . '/path-converter/src/Converter.php'; -if($type == 'javascript') +if($type == 'javascript' && defined('config_debug') && !config_debug) array_unshift($request['files'], 'console.log = function() {};'); foreach($request['files'] as $fileName) { diff --git a/changelog.md b/changelog.md index ddb4700c..86118cf0 100755 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ - docu page design>icons shows all icons in dropins/icons/ws (or .../sw, whatever is configured) - Highcharts module boost.js is activated on demand for series with 5000 or more data points to speed up rendering (plot.period, plot.sparkline). In plot.xyplot we can not do this since there is no size info. But most likely list items with xy-data are not so big. - templatechecker now checks the twig syntax of html pages +- console.log is activated in cache mode if parameter "debug" is set to "1" in config.ini ### Updated Libraries From 903547812db4a8185cf0681e4d8b4474aea3d495 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sun, 24 Mar 2024 17:00:40 +0100 Subject: [PATCH 044/112] fix notify.add() --- changelog.md | 1 + lib/base/base.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 86118cf0..ff92bd5d 100755 --- a/changelog.md +++ b/changelog.md @@ -39,6 +39,7 @@ - template checker gave faulty replacement hint for removed widget basic.text - template checker threw errors if an item property was not in the properties class - plot.period and plot.xyplot drawed the plots twice after series update +- notify.add() notification threw errors if message was not of type "text" ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/lib/base/base.js b/lib/base/base.js index 83029008..60eae453 100755 --- a/lib/base/base.js +++ b/lib/base/base.js @@ -1032,7 +1032,7 @@ var notify = { * Add a new message */ add: function (level, signal, title, text, ackitem, ackval) { - if (text.toLowerCase().trim().substr(-16 ) == 'operation failed') + if (text.toString().toLowerCase().trim().substr(-16 ) == 'operation failed') text += '

    Notice:
    The message "operation failed" is thrown if a connect fails before the request can be executed. ' +'Very often, this is due to failed ssl communication. Try calling the service directly in debug mode for more information.

    ' +'Example:
    YourIP/YourSmartvisuDir/lib/weather/service/YourService.php?debug=1'; From 1293d03389d8a3bcd6d5409e424b8ab8e2214cf9 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:38:35 +0100 Subject: [PATCH 045/112] new functions for ioBroker driver - series subscribing/unsubscribing for individual plots (-> plot.timeshift) - busy signal until loading of items is completed --- changelog.md | 3 +- driver/io_iobroker.js | 146 +++++++++++++++++++++++++++++++----------- 2 files changed, 112 insertions(+), 37 deletions(-) diff --git a/changelog.md b/changelog.md index ff92bd5d..be9d9d0b 100755 --- a/changelog.md +++ b/changelog.md @@ -7,8 +7,9 @@ ### Other New Features - improved spline display for the starting point in device.uzsugraph - new icons scene_cooking_drink and scene_robovac_dock (thanks to @Pacifia15)) -- smarthomeNG driver: new configurable option "signalBusy" lets "VISU" logo in the top-right corner blink after a new page is loaded (i.e. "monitor" command has been sent) until all subscribed items have been received. +- smarthomeNG and ioBroker drivers: new configurable option "signalBusy" lets "VISU" logo in the top-right corner blink after a new page is loaded (i.e. "monitor" command has been sent) until all subscribed items have been received. - new weatherservice open-meteo.com +- ioBroker driver now supports subscribing and unsubscribing series for individual plots which enables usage of plot.timeshift with ioBroker (thanks to YellowFlash for testing!) ### Improvements - allowed database modes for series moved into the individual backend drivers diff --git a/driver/io_iobroker.js b/driver/io_iobroker.js index 8b862848..ca032a95 100644 --- a/driver/io_iobroker.js +++ b/driver/io_iobroker.js @@ -19,7 +19,6 @@ * @hide driver_username * @hide driver_password * @hide driver_loopback - * @hide driver_signalBusy * @hide sv_hostname */ @@ -204,7 +203,9 @@ var io = { * - total = on * - count */ - aggregates: ['avg', 'min', 'max', 'on', 'count'], + aggregates: ['avg', 'average', 'min', 'max', 'total', 'on', 'count'], + monitorComplete: null, + openItems: [], /** @@ -262,6 +263,11 @@ var io = { io.socket.on('stateChange', function (id, state) { if (!id || state === null || typeof state !== 'object') return; io.stateChanged(id, state); + if (io.monitorCompleted == false && io.openItems.length == 0){ + io.monitorCompleted = true; + $('.smartvisu .visu').removeClass('blink'); + } + /* if (that._connCallbacks.onCommand && id === that.namespace + '.control.command') { if (state.ack) return; @@ -319,12 +325,23 @@ var io = { val.push([state.ts, state.val]); }); widget.update(plot[1], val); + io.openItems.removeEntry(plot[1]); }); }, - delayPlots: function() { + subscribePlots: function(plotitem) { + var plotItems = []; var items = Array(); - io.plots.forEach(plot => { + + if (plotitem == undefined) + plotItems = io.plots; + else + plotItems = plotitem; + + //DEBUG: + console.log('[io_iobroker] subscribing series: ', plotItems); + + plotItems.forEach(plot => { io.updatePlot(plot); plot[2] = true; // enable stateChanged items.push(plot[0]); @@ -337,6 +354,7 @@ var io = { }, monitor: function() { + io.monitorCompleted = false; io.listeners = []; var listeners = widget.listeners(); var listenItem; @@ -348,35 +366,23 @@ var io = { io.listeners[listenItem] = listeners[i]; } var items = Object.keys(io.listeners); + io.openItems = Object.keys(io.listeners); + - if (io.checkConnected() && items.length) { - io.socket.emit('subscribe', items); - io.read(items); + if (io.checkConnected()) { + if (items.length) { + io.socket.emit('subscribe', items); + io.read(items); + } // plot io.plots = Array(); clearTimeout(io.firstPlotTimeout); io.firstPlotTimeout = 0; - var unique = Array(); - widget.plot().each(function (idx) { - var items = widget.explode($(this).attr('data-item')); - $.each(items, function() { - var item = String(this); - var pt = item.split('.'); - if (!unique[item] && (pt instanceof Array) && widget.checkseries(item)) { - unique[item] = 1; - if (io.plots.find(plot => plot[1] === item) === undefined) { - var id = item.substr(0, item.length - 4 - pt[pt.length - 4].length - pt[pt.length - 3].length - pt[pt.length - 2].length - pt[pt.length - 1].length); - io.plots.push([id, item, false]); - } - io.firstPlotTimeout = -1; - } - }); - }); + io.startseries(); - // Start subscribing and draw with a delay in the plots so the "normal" widgets are populated with data first. - // This speeds up page loading when there is a lot of data in the plots. - if (io.firstPlotTimeout == -1) io.firstPlotTimeout = setTimeout(io.delayPlots, 1000); + if (sv.config.driver.signalBusy) + $('.smartvisu .visu').addClass('blink'); } }, @@ -432,25 +438,93 @@ var io = { } widget.update(item, val); + io.openItems.removeEntry(item); if (item != io.listeners[item]) widget.update(io.listeners[item], val); } }, /** - * stop all subscribed series + * start subscriptions for all plots in a page or a single specified plot */ - stopseries: function () { - if (io.isConnected) { - var items = io.listeners != [] ? Object.keys(io.listeners) : widget.listeners(); - io.plots.forEach(plot => { - items.push(plot[0]); + startseries: function(plotwidget){ + var unique = Array(); + var plotWidgets = []; + var plotsLength = io.plots.length; + var reorderPlots = []; + if (plotwidget === undefined) + plotWidgets = widget.plot(); + else + plotWidgets = plotwidget; + + plotWidgets.each(function (idx) { + var items = widget.explode($(this).attr('data-item')); + $.each(items, function() { + var item = String(this); + var pt = item.split('.'); + if (!unique[item] && (pt instanceof Array) && widget.checkseries(item)) { + unique[item] = 1; + var id = item.substr(0, item.length - 4 - pt[pt.length - 4].length - pt[pt.length - 3].length - pt[pt.length - 2].length - pt[pt.length - 1].length); + if (io.plots.find(plot => plot[1] === item) === undefined) { + io.plots.push([id, item, false]); + io.openItems.push(item); + } + else + reorderPlots.push([id, item, false]); + + io.firstPlotTimeout = -1; + } }); - io.socket.emit('unsubscribe', items); - io.read(items); + }); + if (plotwidget == undefined){ + // Start subscribing the plots with delay so the "normal" widgets are populated with data first. + // This speeds up page loading when there is a lot of data in the plots. + if (io.firstPlotTimeout == -1) + io.firstPlotTimeout = setTimeout(io.subscribePlots, 1000); + } + else { + io.subscribePlots(reorderPlots.concat(io.plots.slice(plotsLength))); + } + }, + + + /** + * stop subscriptions for all plots in a page or a single specified plot + * plotwidget is the jQuery object representing a specific plot widget + */ + stopseries: function (plotwidget) { + var items = Array(); + if (io.isConnected) { + if (plotwidget != undefined){ + var plotitems = widget.explode(plotwidget.attr('data-item')); + $.each(plotitems, function() { + if (widget.plot(this).length == 1){ // stop series if plotitem is used only once + var item = String(this); + var plotIndex = io.plots.findIndex(plot => plot[1] === item); + //DEBUG: + console.log('[io_iobroker] cancelling series: '+ item + ' at index: ' + plotIndex); + + // delete entry in the plots array so plot update is skipped even if base item is updated + io.plots.splice(plotIndex, 1); + delete widget.buffer[item]; + } + }) + } + else { + // all items - Cancelling on page change is OK but we should find a more suitable location (ToDo) + items = io.listeners != [] ? Object.keys(io.listeners) : widget.listeners(); + + io.valueType = Array(); // clear list + // all series items + io.plots.forEach(plot => { + items.push(plot[0]); + }); + io.socket.emit('unsubscribe', items); + io.read(items); + } } - valueType = Array(); // clear list - } + + }, /* logout: function() { From 8eecec0604f611c4cbf7a1b7af885a64323e8480 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Thu, 11 Apr 2024 19:44:10 +0200 Subject: [PATCH 046/112] new app for analysing plots --- apps/app_analyse_plots.html | 351 ++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 apps/app_analyse_plots.html diff --git a/apps/app_analyse_plots.html b/apps/app_analyse_plots.html new file mode 100644 index 00000000..357f471a --- /dev/null +++ b/apps/app_analyse_plots.html @@ -0,0 +1,351 @@ +/** +* ----------------------------------------------------------------------------- +* @package smartVISU +* @author Wolfram v. Hülsen +* @copyright 2012 - 2024 +* @license GPL [http://www.gnu.de] +* @version 1.0 +* +* @title Plot Analyser +* @category visu +* @icon icons/ws/measure_power_meter.svg +* @color #b00 +* @description Analyse plots with flexible configuration of series parameters +* +* ----------------------------------------------------------------------------- +*/ + + +{% extends "apps.html" %} + +{% block sidebar %} + +
    +
    +
    Plot Analyser
    +
    +

    Plot Analyser

    +
    + +

    Global Plot Settings:

    +
    +
    + + +
    +
    +
    + + +
    +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Y Axis 1Y Axis 2Y Axis 3Y Axis 4
    Activate axis
    Y-axis min value
    Y-axis max value
    Y-axis position
    Y-axis scale type
    Y-axis unit
    + +
    + + +
    +
    +

    Item specific Settings:

    + {% + set sources = { + '1': 'Item 1', + '2': 'Item 2', + '3': 'Item 3', + '4': 'Item 4', + '5': 'Item 5' + } + %} + +
    +
    +
      + {% for source, title in sources %} +
    • {{ title }}
    • + {% endfor %} +
    +
    +
    + {% for source, title in sources %} +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + {% endfor %} +
    +
    +
    + + +{% endblock %} + + +{% block content %} + + + + {% set mode = "raw" %} /** just init to activate boost mode */ + {% set zoom = "advanced" %} /** enable navigator */ + {% set export = 2 %} /** activate export menu */ + +
    + + {{ plot.period('analyse-plotpopup', item, mode, tmin, tmax, ymin, ymax, count, label, color, exposure, axis, zoom, assign, opposite, ycolor, ytype, unit, chartoptions, stacking, stacks, export, source) }} +
    + +/** options not (yet) used + data-axis = [ x axix name and y axis name(s) ] + data-ycolor = [colors of y axes ] +*/ + + + + +
    +{% endblock %} + + From 1a780c9e2a555d35c3a34d90d5eb0f8d29ec11bd Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:37:56 +0200 Subject: [PATCH 047/112] display final widget code in app analyse_plots --- apps/app_analyse_plots.html | 59 +++++++++++++++++++++++++++++++------ changelog.md | 1 + 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/apps/app_analyse_plots.html b/apps/app_analyse_plots.html index 357f471a..33cf0fd6 100644 --- a/apps/app_analyse_plots.html +++ b/apps/app_analyse_plots.html @@ -166,13 +166,23 @@

    Item specific Settings:

    {% block content %} {% set mode = "raw" %} /** just init to activate boost mode */ @@ -182,6 +192,13 @@

    Item specific Settings:

    {{ plot.period('analyse-plotpopup', item, mode, tmin, tmax, ymin, ymax, count, label, color, exposure, axis, zoom, assign, opposite, ycolor, ytype, unit, chartoptions, stacking, stacks, export, source) }} +
    +

    Generated widget code:

    +
    + {% filter trim|escape|nl2br %}{% verbatim %} + {{ plot.period(...) }} + {% endverbatim %}{% endfilter %} +
    /** options not (yet) used @@ -216,7 +233,7 @@

    Item specific Settings:

    if (io.stopseries ) io.stopseries(plot); that.element.highcharts().destroy(); - var apSeries = "", apCount = "", apExposure = "", apColor = "", apMode = "", apLabel = "", apAssign = "", apStacking = "", apStacks = ""; + var apSeries = "", apCount = "", apExposure = "", apColor = "", apMode = "", apLabel = "", apAssign = "", apStacking = "", apStacks = "", apBaseItems = ""; // Data source: database or item var apDataSource = $('#apDataSource').val().toString() || 'database'; @@ -227,13 +244,18 @@

    Item specific Settings:

    //y-axis parameters for 4 axes var apYmins = [], apYmaxs = [], apYtypes = [],apYposs = [], apYunits = []; + // check active axis with highest number for (var i=1; i<5; i++){ - console.log($('#apYact'+i).val().toString()) - apYmins[i-1] = $('#apYact'+i).prop('checked') ? $('#apYmin'+i).val().toString() || '' : ''; - apYmaxs [i-1] = $('#apYact'+i).prop('checked') ? $('#apYmax'+i).val().toString() || '' : ''; - apYtypes[i-1] = $('#apYact'+i).prop('checked') ? $('#apYtype'+i).val().toString() || '' : ''; - apYposs[i-1] = $('#apYact'+i).prop('checked') ? $('#apYpos' +i).val().toString() || '' : ''; - apYunits[i-1] = $('#apYact'+i).prop('checked') ? $('#apYunit' +i).val().toString() || '' : ''; + if ($('#apYact'+i).prop('checked')) + var apMaxYaxis = i; + } + // evaluate settings up to highest active axis + for (var i=1; iItem specific Settings: apAssign += delimiter + apAssigns[idx]; apStacking += delimiter + apStackings[idx]; apStacks += delimiter + apStackss[idx]; + apBaseItems += delimiter + apItem; }) } @@ -340,7 +363,25 @@

    Item specific Settings:

    // start new series subscription if (io.startseries) io.startseries(plot); - }) + + // update widget code display field + var widgetCode = ["''", setWidgetParam(apBaseItems, ''), setWidgetParam(apMode, 'avg'), setWidgetParam(apTmin, '1h'), setWidgetParam(apTmax, 'now'), setWidgetParam(apYmin, ''), setWidgetParam(apYmax, ''), + setWidgetParam(apCount, '100'), setWidgetParam(apLabel, ''), setWidgetParam(apColor, ''), setWidgetParam(apExposure, 'line'), "''", "'advanced'", setWidgetParam(apAssign, ''), + setWidgetParam(apYpos, ''), "''", setWidgetParam(apYtype, 'linear'), setWidgetParam(apYunit, ''), apChartopts ? apChartopts: "''", setWidgetParam(apStacking, 'normal'), setWidgetParam(apStacks, ''), "2", + "'"+apDataSource+"'" ].join(', '); + var widgetCodeHtml = $('#apWidgetCode').html(); + $('#apWidgetCode').html(widgetCodeHtml.replace(/period\(.*\)/, 'period('+widgetCode+')')); + }); + + function setWidgetParam(widgetParam, defaultValue){ + if (widgetParam.split(/,\s*/).every(function(element){return element == defaultValue})) + widgetParam = ''; + var paramLength = widgetParam.split(',').length; + var ret = paramLength > 1 ? "['":"'"; + ret += widgetParam.split(/,\s*/).join("', '"); + ret += paramLength > 1 ? "']" : "'"; + return ret; + } }); diff --git a/changelog.md b/changelog.md index be9d9d0b..f6c610fe 100755 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ - basic.tank is now able to change colors according to reached thresholds - additional option for basic.select to support activity indicator like basic.stateswitch - basic.icon accepts alpha values for rgb(a)/hsl(a)/hsv(a) and also hex rgb(a) values in items +- new app analyse_plots enables live parametrizing of plot.period ### Other New Features - improved spline display for the starting point in device.uzsugraph From 7482e4b014d02635100ceccd4b63e2dfc4cdebc6 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:04:35 +0200 Subject: [PATCH 048/112] generate tooltip with basic.print --- changelog.md | 1 + widgets/basic.html | 11 ++++++----- widgets/basic.js | 12 ++++++++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/changelog.md b/changelog.md index f6c610fe..84b2e9fe 100755 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ - additional option for basic.select to support activity indicator like basic.stateswitch - basic.icon accepts alpha values for rgb(a)/hsl(a)/hsv(a) and also hex rgb(a) values in items - new app analyse_plots enables live parametrizing of plot.period +- basic.print can be used to generate a tooltip on a linked widget (parameter href = widget-id, rel = 'tooltip') ### Other New Features - improved spline display for the starting point in device.uzsugraph diff --git a/widgets/basic.html b/widgets/basic.html index f88355cf..af0b6617 100755 --- a/widgets/basic.html +++ b/widgets/basic.html @@ -337,8 +337,8 @@ * @param {color[?](icon0to5,hidden,blank)=icon0} array of colors; 'icon0' through 'icon5' or e. g. '#f00' for red (optional, default = icon0 ) additionally you can use 'hidden' to not diplay at all or 'blank' to make it invisible but preserve the space that would be used. the first one is the base color for values below first threshold, so pass one color more than thresholds. -* @param {text=} URL to use as link (optional) -* @param {text=} used in combination with href as data-rel attribute {e.g. to open a popup} (optional) +* @param {text=} URL to use as link or id of a widget to add a tooltip (optional) +* @param {text=} use 'tooltip' in combination with a widget id as href in order to attach a tooltip to that widget. Any other text will interpret href as link to a popup (optional) * * @see misc/fundamentals#Array-Form * @@ -347,11 +347,12 @@ */ {% macro print(id, item, format, formula, threshold, color, href, rel) %} {% set color = color|default('icon0') %} -{% if href is empty %}{% else %}{% endif %} +{% if href is not empty and rel != 'tooltip' %}{% endif %} --- - {% if href is empty %}{% else %}{% endif %} + data-thresholds="{{ implode(threshold) }}" data-colors="{{ implode(color) }}" + {% if rel == 'tooltip' %}data-bind="{{ uid(page, href) }}"{% endif %}>--- + {% if href is not empty and rel != 'tooltip' %}{% endif %} {% endmacro %} diff --git a/widgets/basic.js b/widgets/basic.js index 20a52743..c7846f6b 100644 --- a/widgets/basic.js +++ b/widgets/basic.js @@ -779,7 +779,8 @@ $.widget("sv.basic_print", $.sv.widget, { format: "", formula: "", thresholds: "", - colors: "" + colors: "", + bind: "" }, _update: function(response) { @@ -846,7 +847,14 @@ $.widget("sv.basic_print", $.sv.widget, { this.element.html(calc); else this.element.text(calc); - + + if (this.options.bind != ''){ + this.element.hide(); + $('#'+this.options.bind).attr('data-tip', calc); + return; + } + + // colorize var currentIndex = 0; $.each(String(this.options.thresholds).explode(), function(index, threshold) { From 2106d7fa459078477a1b0e71d938ce810433b00c Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:00:53 +0200 Subject: [PATCH 049/112] extend docu page --- pages/docu/basic/widget_basic.print.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pages/docu/basic/widget_basic.print.html b/pages/docu/basic/widget_basic.print.html index d9a8a3ba..8879c3b4 100755 --- a/pages/docu/basic/widget_basic.print.html +++ b/pages/docu/basic/widget_basic.print.html @@ -56,7 +56,19 @@
    Colors
    {{ basic.print('', 'bath.light.value', '', '', '', '#f00') }}
    {{ basic.print('', 'bath.light.value', '', '', [100,200], ['green', '', 'icon1']) }}
    - +
    Usage as Tooltip
    + By giving any other widget an id and referencing this in the parameter href, a dynamic tooltip can be created on that widget by passing the value 'tooltip' to the parameter "rel". Some widgets need to be wrapped in a span containing the id. + Hover with the mouse over the flipswitch to see the effect. (Note: Will not work on Android devices wich do not support hover effects) +
    + {% filter trim|escape|nl2br %}{% verbatim %} + {{ basic.flip('', 'bath.light.switch') }} + {{ basic.print('', 'bath.light.switch', 'text', 'VAR=="1"?"Schaltuhr ist an":"Schalter ist aus"','','','myFlip1','tooltip') }} {% endverbatim %}{% endfilter %} +
    +
    + {{ basic.flip('myFlip1', 'bath.light.switch') }} + {{ basic.print('', 'bath.light.switch', 'text', 'VAR=="1"?"Schalter ist an":"Schalter ist aus"','','','myFlip1','tooltip') }}
    +
    +
    Advanced Scripting
    basic.print can also be used for some "hacks".
    This example shows how to update the color of a widget text based on the value of ANOTHER item. Click on the heater icon and change the temperature. The temperature print will turn red if the heater is off and green if it is on.
    From 5cd06198850675203f7d0e41397d314c3ae97895 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Wed, 24 Apr 2024 13:40:46 +0200 Subject: [PATCH 050/112] add color and indicator options to basic.trigger --- changelog.md | 2 ++ widgets/basic.html | 21 ++++++++++++++++----- widgets/basic.js | 24 +++++++++++++++++++++++- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 84b2e9fe..cd125428 100755 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ - basic.icon accepts alpha values for rgb(a)/hsl(a)/hsv(a) and also hex rgb(a) values in items - new app analyse_plots enables live parametrizing of plot.period - basic.print can be used to generate a tooltip on a linked widget (parameter href = widget-id, rel = 'tooltip') +- additional color and indicator options for basic.trigger ### Other New Features - improved spline display for the starting point in device.uzsugraph @@ -43,6 +44,7 @@ - template checker threw errors if an item property was not in the properties class - plot.period and plot.xyplot drawed the plots twice after series update - notify.add() notification threw errors if message was not of type "text" +- basic.trigger showed oversized button instead of specified type "icon" ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/widgets/basic.html b/widgets/basic.html index af0b6617..59e11d87 100755 --- a/widgets/basic.html +++ b/widgets/basic.html @@ -733,18 +733,29 @@ * @param {text=1} value to send if the button is pressed (optional, default 1) * @param {type=mini} type: 'micro', 'mini', 'midi' (optional, default: mini) * @param {text(button,page,both)=button} trigger event: 'button', 'page', 'both' (optional, default: button) +* @param {color(icon0to5)=} single color e. g. '#f00' for red or icon class. (optional) +* @param {color(icon0to5,blink,:)=} activity indicator which is active until the timeout is reached; pass either a color, icon class or 'blink'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) */ -{% macro trigger(id, name, txt, icon, val, type, event) %} +{% macro trigger(id, name, txt, icon, val, type, event, color, indicator) %} {% import _self as basic %} + {% set iconstyles = ['icon0', 'icon1', 'icon2', 'icon3', 'icon4', 'icon5'] %} + {% if ":" in indicator %} + {% set indicatorparms = indicator|split(":") %} + {% set indicator = indicatorparms[0] %} + {% set indicator_duration = indicatorparms[1] %} + {% else %} + {% set indicator_duration = config_indicator_duration|default(3) %} + {% endif %}
  • {{ lib.svgimg ('', 'scene_sleeping_alternat.svg', 'icon0', '') }}

    Sleeping

    - -
    +
    +
    + /** place to insert widgets on the right end of the menu button */ +
  • /** @@ -30,9 +32,9 @@ *
  • * * Sleeping

    Sleeping

    +*
    * *
    -* *
  • */ diff --git a/pages/base/base.css b/pages/base/base.css index 0424e6c7..786bc81e 100644 --- a/pages/base/base.css +++ b/pages/base/base.css @@ -279,6 +279,7 @@ a:hover { .ui-li-aside { font-size: 0.8em; /* 0.875em; */ right: .6em !important; + z-index: 3; } .ui-li-aside .icon { @@ -1752,10 +1753,6 @@ div.ui-slider-switch{ .plot.plot-highstock { height: 315px; } -.plot.plot-highstock { - height: 315px; -} - .block .ui-fixed .plot, .block .set-1 .plot { min-height: 14.5em; diff --git a/pages/base/quad.css b/pages/base/quad.css index 72eff25d..c12372a4 100755 --- a/pages/base/quad.css +++ b/pages/base/quad.css @@ -279,10 +279,12 @@ left: 7px; right: 0px; position: absolute; + z-index:3; } .quad_tiles .ui-li-aside { top: 0 !important; + z-index:3; } .quad_list a { diff --git a/pages/example1.smarthome/navigation.html b/pages/example1.smarthome/navigation.html index e08abf07..90f287d4 100755 --- a/pages/example1.smarthome/navigation.html +++ b/pages/example1.smarthome/navigation.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -16,13 +16,13 @@

    Energieverbrauch

    -
    - {{ basic.print('WirkleistungGesamt', 'Allgemein.Zaehler.LeistungGesamt', 'W') }}
    - {{ basic.print('WasserTagesverbrauch', 'Allgemein.Zaehler.WasserTag', 'l') }} + +
    + {{ basic.print('WirkleistungGesamt', 'Allgemein.Zaehler.LeistungGesamt', 'W') }}
    + {{ basic.print('WasserTagesverbrauch', 'Allgemein.Zaehler.WasserTag', 'l') }} -
    +
    -
  • Erdgeschoss
  • @@ -30,16 +30,16 @@

    Energieverbrauch

    Esszimmer / Küche

    - -
    - {{ basic.print('TemperaturEsszimmer', 'EG.Esszimmer.temperature', '°C') }}
    - {{ basic.symbol('FensterSuedEssen', 'EG.Esszimmer.Fenster1', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensterWestEssen', 'EG.Esszimmer.Fenster2', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensterKueche', 'EG.Esszimmer.Fenster3', '', 'fts_window_1w_open.svg', 0, '', 'icon1') }} - {{ basic.symbol('TuerEssen', 'EG.Esszimmer.Tuer', '', 'fts_door_open.svg', 0, '', 'icon1') }} - {{ basic.print('heiztmitEGEssen', 'EG.Esszimmer.temperature.stellgroesse', '%') }} -
    + +
    + {{ basic.print('TemperaturEsszimmer', 'EG.Esszimmer.temperature', '°C') }}
    + {{ basic.symbol('FensterSuedEssen', 'EG.Esszimmer.Fenster1', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensterWestEssen', 'EG.Esszimmer.Fenster2', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensterKueche', 'EG.Esszimmer.Fenster3', '', 'fts_window_1w_open.svg', 0, '', 'icon1') }} + {{ basic.symbol('TuerEssen', 'EG.Esszimmer.Tuer', '', 'fts_door_open.svg', 0, '', 'icon1') }} + {{ basic.print('heiztmitEGEssen', 'EG.Esszimmer.temperature.stellgroesse', '%') }} +
  • @@ -66,30 +66,29 @@

    Bad / WC

    Flure / Nebenräume

    - -
    - {{ basic.symbol('FlurEingangHeizen', 'EG.FlureNebenraume.Heizung1', '', 'sani_heating.svg', 1, '', 'icon1') }} - {{ basic.print('FlurEingangTemperatur', 'EG.FlureNebenraume.temperature1', '°C') }}
    - {{ basic.symbol('FlurDieleHeizen', 'EG.FlureNebenraume.Heizung2', '', 'sani_heating.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensterFluroffenRegen', 'Allgemein.Alarm.RegenFensterEgOg', '', 'message_attention.svg', 1, '', 'icon1') }} - {{ basic.print('FlurDieleTemperatur', 'EG.FlureNebenraume.temperature2', '°C') }} -
    + +
    + {{ basic.symbol('FlurEingangHeizen', 'EG.FlureNebenraume.Heizung1', '', 'sani_heating.svg', 1, '', 'icon1') }} + {{ basic.print('FlurEingangTemperatur', 'EG.FlureNebenraume.temperature1', '°C') }}
    + {{ basic.symbol('FlurDieleHeizen', 'EG.FlureNebenraume.Heizung2', '', 'sani_heating.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensterFluroffenRegen', 'Allgemein.Alarm.RegenFensterEgOg', '', 'message_attention.svg', 1, '', 'icon1') }} + {{ basic.print('FlurDieleTemperatur', 'EG.FlureNebenraume.temperature2', '°C') }} +
  • Aussenanlage

    - -
    - {{ basic.print('AussenTemperatur', 'EG.Aussen.temperature', '°C') }}
    - {{ basic.symbol('AussenRegen', 'EG.Aussen.Regen', '', 'weather_rain.svg', 1, '', 'icon1') }} - {{ basic.symbol('HaustuerVerriegelt', 'EG.Aussen.HaustuerRiegel', '', 'status_locked.svg', 1, '', 'icon1') }} - {{ basic.symbol('HaustuerOffen', 'EG.Aussen.Haustuer', '', 'fts_door_open.svg', 0, '', 'icon1') }} - -
    + +
    + {{ basic.print('AussenTemperatur', 'EG.Aussen.temperature', '°C') }}
    + {{ basic.symbol('AussenRegen', 'EG.Aussen.Regen', '', 'weather_rain.svg', 1, '', 'icon1') }} + {{ basic.symbol('HaustuerVerriegelt', 'EG.Aussen.HaustuerRiegel', '', 'status_locked.svg', 1, '', 'icon1') }} + {{ basic.symbol('HaustuerOffen', 'EG.Aussen.Haustuer', '', 'fts_door_open.svg', 0, '', 'icon1') }} +
  • Obergeschoss
  • @@ -97,45 +96,45 @@

    Aussenanlage

    Johanna

    - -
    - {{ basic.print('TemperaturJohanna', 'OG.Johanna.temperature', '°C') }}
    - {{ basic.symbol('FensterJohanna', 'OG.Johanna.Fenster', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('TuerJohanna', 'OG.Johanna.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} - {{ basic.print('heiztmitJohanna', 'OG.Johanna.temperature.stellgroesse', '%') }} -
    + +
    + {{ basic.print('TemperaturJohanna', 'OG.Johanna.temperature', '°C') }}
    + {{ basic.symbol('FensterJohanna', 'OG.Johanna.Fenster', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('TuerJohanna', 'OG.Johanna.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} + {{ basic.print('heiztmitJohanna', 'OG.Johanna.temperature.stellgroesse', '%') }} +
  • Büro Ingrid

    - -
    - {{ basic.print('TemperaturPhilipp', 'OG.Philipp.temperature', '°C') }}
    - {{ basic.symbol('FensterPhilipp', 'OG.Philipp.Fenster', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('TuerPhilipp', 'OG.Philipp.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} - {{ basic.print('heiztmitPhilipp', 'OG.Philipp.temperature.stellgroesse', '%') }} -
    + +
    + {{ basic.print('TemperaturPhilipp', 'OG.Philipp.temperature', '°C') }}
    + {{ basic.symbol('FensterPhilipp', 'OG.Philipp.Fenster', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('TuerPhilipp', 'OG.Philipp.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} + {{ basic.print('heiztmitPhilipp', 'OG.Philipp.temperature.stellgroesse', '%') }} +
  • Schlafen / Ankleide

    +
    -
    - {{ basic.symbol('FensterSued', 'OG.Schlafen.Fenster1', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensterWest', 'OG.Schlafen.Fenster2', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('TuerSchlafen', 'OG.Schlafen.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} - {{ basic.print('SchlafenTemperatur', 'OG.Schlafen.temperature', '°C') }}
    - {{ basic.symbol('FensterAnkleide', 'OG.Ankleide.Fenster', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.print('AnkleideTemperatur', 'OG.Ankleide.temperature', '°C') }} +
    + {{ basic.symbol('FensterSued', 'OG.Schlafen.Fenster1', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensterWest', 'OG.Schlafen.Fenster2', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('TuerSchlafen', 'OG.Schlafen.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} + {{ basic.print('SchlafenTemperatur', 'OG.Schlafen.temperature', '°C') }}
    + {{ basic.symbol('FensterAnkleide', 'OG.Ankleide.Fenster', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.print('AnkleideTemperatur', 'OG.Ankleide.temperature', '°C') }} -
    - +
  • Dachgeschoss
  • @@ -143,34 +142,33 @@

    Schlafen / Ankleide

    Wohnen

    - -
    - {{ basic.print('TemperaturDGWohnen', 'DG.Wohnen.temperature', '°C') }}
    - {{ basic.symbol('FensterDGNord', 'DG.Wohnen.FensterNord', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensterDGSuedlinks', 'DG.Wohnen.FensterSuedlinks', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensterDGSuedrechts', 'DG.Wohnen.FensterSuedrechts', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('TuerDG', 'DG.Wohnen.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensteroffenDGWohRegen', 'Allgemein.Alarm.RegenFensterDGWohnen', '', 'message_attention.svg', 1, '', 'icon1') }} - {{ basic.print('heiztmitDGWohnen', 'DG.Wohnen.stellgroesse', '%') }} - -
    + +
    + {{ basic.print('TemperaturDGWohnen', 'DG.Wohnen.temperature', '°C') }}
    + {{ basic.symbol('FensterDGNord', 'DG.Wohnen.FensterNord', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensterDGSuedlinks', 'DG.Wohnen.FensterSuedlinks', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensterDGSuedrechts', 'DG.Wohnen.FensterSuedrechts', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('TuerDG', 'DG.Wohnen.Tuer', '', 'fts_door_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensteroffenDGWohRegen', 'Allgemein.Alarm.RegenFensterDGWohnen', '', 'message_attention.svg', 1, '', 'icon1') }} + {{ basic.print('heiztmitDGWohnen', 'DG.Wohnen.stellgroesse', '%') }} +
  • Schlafen

    +
    -
    - {{ basic.print('TemperaturDgSchlafen', 'DG.Schlafen.temperature', '°C') }}
    - {{ basic.symbol('Fensterlinks', 'DG.Schlafen.Fensterlinks', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('Fensterrechts', 'DG.Schlafen.Fensterrechts', '', 'fts_door_open.svg', 1, '', 'icon1') }} - {{ basic.symbol('FensteroffenDGSchRegen', 'Allgemein.Alarm.RegenFensterDGSchlafen', '', 'message_attention.svg', 1, '', 'icon1') }} - {{ basic.print('heiztmitDGSchlafen', 'DG.Schlafen.stellgroesse', '%') }} +
    + {{ basic.print('TemperaturDgSchlafen', 'DG.Schlafen.temperature', '°C') }}
    + {{ basic.symbol('Fensterlinks', 'DG.Schlafen.Fensterlinks', '', 'fts_window_1w_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('Fensterrechts', 'DG.Schlafen.Fensterrechts', '', 'fts_door_open.svg', 1, '', 'icon1') }} + {{ basic.symbol('FensteroffenDGSchRegen', 'Allgemein.Alarm.RegenFensterDGSchlafen', '', 'message_attention.svg', 1, '', 'icon1') }} + {{ basic.print('heiztmitDGSchlafen', 'DG.Schlafen.stellgroesse', '%') }} -
    - +
  • diff --git a/pages/example2.knxd/rooms_menu.html b/pages/example2.knxd/rooms_menu.html index 98c00677..7c03cb31 100644 --- a/pages/example2.knxd/rooms_menu.html +++ b/pages/example2.knxd/rooms_menu.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß -* @copyright 2012 - 2015 +* @copyright 2012 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -14,70 +14,70 @@
  • Wohnzimmer

    -
    - {{ basic.print('wohnen_temperatur', '0/0/4/9.xxx', '°') }}
    - {{ basic.symbol('wohnen_licht_on', ['0/1/2/1.001', '0/1/12/1.001','0/1/22/1.001', '0/1/32/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('wohnen_licht_off', ['0/1/2/1.001', '0/1/12/1.001','0/1/22/1.001', '0/1/32/1.001'], '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('wohnen_heizung_on', '0/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('wohnen_heizung_off', '0/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('wohnen_temperatur', '0/0/4/9.xxx', '°') }}
    + {{ basic.symbol('wohnen_licht_on', ['0/1/2/1.001', '0/1/12/1.001','0/1/22/1.001', '0/1/32/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('wohnen_licht_off', ['0/1/2/1.001', '0/1/12/1.001','0/1/22/1.001', '0/1/32/1.001'], '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('wohnen_heizung_on', '0/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('wohnen_heizung_off', '0/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Küche

    -
    - {{ basic.print('kueche_temperatur', '1/0/4/9.xxx', '°') }}
    - {{ basic.symbol('kueche_licht_on', ['1/1/2/1.001', '1/1/12/1.001', '1/1/22/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('kueche_licht_off', ['1/1/2/1.001', '1/1/12/1.001', '1/1/22/1.001'], '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('kueche_heizung_on', '1/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('kueche_heizung_off', '1/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('kueche_temperatur', '1/0/4/9.xxx', '°') }}
    + {{ basic.symbol('kueche_licht_on', ['1/1/2/1.001', '1/1/12/1.001', '1/1/22/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('kueche_licht_off', ['1/1/2/1.001', '1/1/12/1.001', '1/1/22/1.001'], '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('kueche_heizung_on', '1/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('kueche_heizung_off', '1/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Gästezimmer

    -
    - {{ basic.print('gaeste_temperatur', '2/0/4/9.xxx', '°') }}
    - {{ basic.symbol('gaeste_licht_off', '2/1/2/1.001', '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('gaeste_licht_on', '2/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('gaeste_heizung_on', '2/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('gaeste_heizung_off', '2/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('gaeste_temperatur', '2/0/4/9.xxx', '°') }}
    + {{ basic.symbol('gaeste_licht_off', '2/1/2/1.001', '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('gaeste_licht_on', '2/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('gaeste_heizung_on', '2/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('gaeste_heizung_off', '2/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Gäste-WC

    -
    - {{ basic.print('gaestewc_temperatur', '3/0/4/9.xxx', '°') }}
    - {{ basic.symbol('gaestewc_licht_off', '3/1/2/1.001', '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('gaestewc_licht_on', '3/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('gaestewc_heizung_on', '3/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('gaestewc_heizung_off', '3/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('gaestewc_temperatur', '3/0/4/9.xxx', '°') }}
    + {{ basic.symbol('gaestewc_licht_off', '3/1/2/1.001', '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('gaestewc_licht_on', '3/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('gaestewc_heizung_on', '3/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('gaestewc_heizung_off', '3/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Hauswirtschaftsraum

    -
    - {{ basic.symbol('hwr_licht', '5/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} -
    +
    + {{ basic.symbol('hwr_licht', '5/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} +
  • Diele/Treppe

    -
    - {{ basic.print('diele_temperatur', '4/0/4/9.xxx', '°') }}
    - {{ basic.symbol('diele_licht_on', ['4/1/2/1.001', '4/1/12/1.001', '4/1/22/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('diele_licht_off', ['4/1/2/1.001', '4/1/12/1.001', '4/1/22/1.001'], '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('diele_heizung_on', '4/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('diele_heizung_off', '4/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('diele_temperatur', '4/0/4/9.xxx', '°') }}
    + {{ basic.symbol('diele_licht_on', ['4/1/2/1.001', '4/1/12/1.001', '4/1/22/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('diele_licht_off', ['4/1/2/1.001', '4/1/12/1.001', '4/1/22/1.001'], '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('diele_heizung_on', '4/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('diele_heizung_off', '4/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • @@ -85,62 +85,62 @@
  • Schlafzimmer

    -
    - {{ basic.print('schlafen_temperatur', '6/0/4/9.xxx', '°') }}
    - {{ basic.symbol('schlafen_licht_on', '6/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('schlafen_licht_off', '6/1/2/1.001', '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('schlafen_heizung_on', '6/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('schlafen_heizung_off', '6/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('schlafen_temperatur', '6/0/4/9.xxx', '°') }}
    + {{ basic.symbol('schlafen_licht_on', '6/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('schlafen_licht_off', '6/1/2/1.001', '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('schlafen_heizung_on', '6/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('schlafen_heizung_off', '6/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Kinderzimmer

    -
    - {{ basic.print('kind_temperatur', '7/0/4/9.xxx', '°') }}
    - {{ basic.symbol('kind_licht_on', ['7/1/2/1.001','7/1/12/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('kind_licht_off', ['7/1/2/1.001','7/1/12/1.001'], '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('kind_heizung_on', '7/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('kind_heizung_off', '7/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('kind_temperatur', '7/0/4/9.xxx', '°') }}
    + {{ basic.symbol('kind_licht_on', ['7/1/2/1.001','7/1/12/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('kind_licht_off', ['7/1/2/1.001','7/1/12/1.001'], '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('kind_heizung_on', '7/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('kind_heizung_off', '7/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Arbeitszimmer

    -
    - {{ basic.print('arbeiten_temperatur', '8/0/4/9.xxx', '°') }}
    - {{ basic.symbol('arbeiten_licht_on', '8/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('arbeiten_licht_off', '8/1/2/1.001', '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('arbeiten_heizung_on', '8/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('arbeiten_heizung_off', '8/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('arbeiten_temperatur', '8/0/4/9.xxx', '°') }}
    + {{ basic.symbol('arbeiten_licht_on', '8/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('arbeiten_licht_off', '8/1/2/1.001', '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('arbeiten_heizung_on', '8/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('arbeiten_heizung_off', '8/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Bad

    -
    - {{ basic.print('bad_temperatur', '9/0/4/9.xxx', '°') }}
    - {{ basic.symbol('bad_licht_on', ['9/1/2/1.001','9/1/12/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('bad_licht_off', ['9/1/2/1.001','9/1/12/1.001'], '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('bad_heizung_on', '9/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('bad_heizung_off', '9/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('bad_temperatur', '9/0/4/9.xxx', '°') }}
    + {{ basic.symbol('bad_licht_on', ['9/1/2/1.001','9/1/12/1.001'], '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('bad_licht_off', ['9/1/2/1.001','9/1/12/1.001'], '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('bad_heizung_on', '9/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('bad_heizung_off', '9/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • Flur

    -
    - {{ basic.print('flur_temperatur', '10/0/4/9.xxx', '°') }}
    - {{ basic.symbol('flur_licht_on', '10/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('flur_licht_off', '10/1/2/1.001', '', 'light_light.svg', 0, 'and') }} - {{ basic.symbol('flur_heizung_on', '10/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} - {{ basic.symbol('flur_heizung_off', '10/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} -
    +
    + {{ basic.print('flur_temperatur', '10/0/4/9.xxx', '°') }}
    + {{ basic.symbol('flur_licht_on', '10/1/2/1.001', '', 'light_light.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('flur_licht_off', '10/1/2/1.001', '', 'light_light.svg', 0, 'and') }} + {{ basic.symbol('flur_heizung_on', '10/0/2/1.001', '', 'sani_floor_heating.svg', 1, 'or', 'icon1') }} + {{ basic.symbol('flur_heizung_off', '10/0/2/1.001', '', 'sani_floor_heating.svg', 0) }} +
  • diff --git a/pages/example3.graphic/navigation.html b/pages/example3.graphic/navigation.html index 8d894a01..a489521a 100644 --- a/pages/example3.graphic/navigation.html +++ b/pages/example3.graphic/navigation.html @@ -2,212 +2,213 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Martin Gleiß, Wolfram v. Hülsen - * @copyright 2012,2019 + * @copyright 2012- 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ diff --git a/pages/example4.quad/index.html b/pages/example4.quad/index.html index 6f8a8166..7e4836a0 100755 --- a/pages/example4.quad/index.html +++ b/pages/example4.quad/index.html @@ -2,7 +2,7 @@ * ----------------------------------------------------------------------------- * @package smartVISU * @author Onkel Andy - * @copyright 2020 + * @copyright 2020-2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- */ @@ -32,52 +32,53 @@ /**LICHT**************/
  • - - {{ basic.symbol('licht', '', 'Licht', 'light_light.svg', '', '', 'icon0') }} -
    -
    -
    + + {{ basic.symbol('licht', '', 'Licht', 'light_light.svg', '', '', 'icon0') }} + +
    +
    +
  • /**BWMs*************/
  • - - {{ basic.symbol('bwm', '', 'BWM', 'message_presence.svg', '', '', 'icon0') }} -
    -
    -
    + + {{ basic.symbol('bwm', '', 'BWM', 'message_presence.svg', '', '', 'icon0') }} + +
    +
  • /**SWITCHES***************/
  • - - {{ basic.symbol('switches', '', 'Switch', 'message_socket.svg', '', '', 'icon0') }} -
    -
    -
    + + {{ basic.symbol('switches', '', 'Switch', 'message_socket.svg', '', '', 'icon0') }} + +
    +
  • /**MULTIMEDIA*********/
  • - - {{ basic.symbol('multimedia', '', 'Multimedia', 'scene_livingroom.svg', '', '', 'icon0') }} -
    -
    -
    + + {{ basic.symbol('multimedia', '', 'Multimedia', 'scene_livingroom.svg', '', '', 'icon0') }} + +
    +
  • /**STATUS***************/
  • - - {{ basic.symbol('outdoor', '', 'Outdoor', 'weather_sun.svg', '', '', 'icon0') }} -
    -
    -
    + + {{ basic.symbol('outdoor', '', 'Outdoor', 'weather_sun.svg', '', '', 'icon0') }} + +
    +
  • /**BLINDS***************/
  • - - {{ basic.symbol('jalousien', '', 'Jalousien', 'fts_shutter_50.svg', '', '', 'icon0') }} -
    -
    -
    + + {{ basic.symbol('jalousien', '', 'Jalousien', 'fts_shutter_50.svg', '', '', 'icon0') }} + +
    +
  • From 61e266484cace3c93e5dfe36eb24df1271dabecf Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Thu, 2 May 2024 19:18:30 +0200 Subject: [PATCH 053/112] add control elements for plot.timeshift --- apps/app_analyse_plots.html | 20 ++++++++++++++++---- changelog.md | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/app_analyse_plots.html b/apps/app_analyse_plots.html index 33cf0fd6..3967f2a4 100644 --- a/apps/app_analyse_plots.html +++ b/apps/app_analyse_plots.html @@ -19,7 +19,7 @@ {% extends "apps.html" %} {% block sidebar %} -
    @@ -192,6 +191,10 @@

    Item specific Settings:

    {{ plot.period('analyse-plotpopup', item, mode, tmin, tmax, ymin, ymax, count, label, color, exposure, axis, zoom, assign, opposite, ycolor, ytype, unit, chartoptions, stacking, stacks, export, source) }} + +
    + timeline shift interval:  {{ plot.timeshift('analyse-timeshift','analyse-plotpopup', '1d','mini', '- 1d +') }} +

    Generated widget code:

    @@ -382,6 +385,15 @@

    Generated widget code:

    ret += paramLength > 1 ? "']" : "'"; return ret; } + + $('#apShift').on('change', function () { + var apShift = $('#apShift').val(); + var timeshift = $('[id*="analyse-timeshift"]'); + var tsWidget = timeshift.data().svWidget; + timeshift.attr('data-step', apShift); + tsWidget.options.step = apShift; + $('#app_analyse_plots-analyse-timeshift')[0].childNodes[1].data = '- ' + apShift + ' +\n\t\t' + }) }); diff --git a/changelog.md b/changelog.md index 3d36e815..2176e20d 100755 --- a/changelog.md +++ b/changelog.md @@ -3,7 +3,7 @@ - basic.tank is now able to change colors according to reached thresholds - additional option for basic.select to support activity indicator like basic.stateswitch - basic.icon accepts alpha values for rgb(a)/hsl(a)/hsv(a) and also hex rgb(a) values in items -- new app analyse_plots enables live parametrizing of plot.period +- new app analyse_plots enables live parametrizing of plot.period plus travelling through time with plot.timeshift - basic.print can be used to generate a tooltip on a linked widget (parameter href = widget-id, rel = 'tooltip') - additional color and indicator options for basic.trigger - plot.sparkline can now open links and popups. Tooltip shows parameter "id" as curve name (label) From 006aa8e25585bba464fdaccf2ef2a03c0d04df7a Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Thu, 2 May 2024 23:11:33 +0200 Subject: [PATCH 054/112] fix uzsutable display in / after supersize mode --- changelog.md | 1 + widgets/lib.html | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 2176e20d..17ef5780 100755 --- a/changelog.md +++ b/changelog.md @@ -47,6 +47,7 @@ - plot.period and plot.xyplot drawed the plots twice after series update - notify.add() notification threw errors if message was not of type "text" - basic.trigger showed oversized button instead of specified type "icon" +- device.uzsutable did not display correctly during and after supersize mode ### Known Bugs - if item contains a stringified number (e.g. with leading zero). widget.set converts it back to numeric format - so basic.print can not print it as text diff --git a/widgets/lib.html b/widgets/lib.html index 846d7000..4b049c1c 100644 --- a/widgets/lib.html +++ b/widgets/lib.html @@ -164,6 +164,7 @@ element.find("h3").removeClass("ui-corner-all"); element.find(".supersize.sizebig").hide(); element.find(".supersize.sizesmall").show(); + element.find('.highcharts-root').removeClass('supersized'); var previousPosition = element.find(".supersize").attr("data-ypos")*1; $.mobile.silentScroll(previousPosition); } @@ -174,7 +175,8 @@ var actualPlotHeight = parseFloat($(this).css('height').replace('px','')); $(this).css('height', actualPlotHeight * sizeFactor); $(this).highcharts().setSize(null, null); - }); + }).end() + .find('[data-widget="device.uzsutable"] .highcharts-root').css('max-height', sizeFactor < 1 ? '' : 400*sizeFactor+"px"); }); {% endmacro %} From 6c8eff8ed00b8c29d1ebd714124d5e6ed23c5644 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Mon, 6 May 2024 12:25:40 +0200 Subject: [PATCH 055/112] new weather service VisualCrossing --- changelog.md | 1 + lang/de.ini | 4 + lang/en-gb.ini | 4 + lang/en.ini | 4 + lang/fr.ini | 4 + lang/it.ini | 4 + lang/nl.ini | 4 + lib/weather/service/visualcrossing.com.md | 15 +++ lib/weather/service/visualcrossing.com.php | 141 +++++++++++++++++++++ 9 files changed, 181 insertions(+) create mode 100644 lib/weather/service/visualcrossing.com.md create mode 100644 lib/weather/service/visualcrossing.com.php diff --git a/changelog.md b/changelog.md index 17ef5780..b18170d9 100755 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,7 @@ - smarthomeNG and ioBroker drivers: new configurable option "signalBusy" lets "VISU" logo in the top-right corner blink after a new page is loaded (i.e. "monitor" command has been sent) until all subscribed items have been received. - new weatherservice open-meteo.com - ioBroker driver now supports subscribing and unsubscribing series for individual plots which enables usage of plot.timeshift with ioBroker (thanks to YellowFlash for testing!) +- new weather service VisualCrossing ### Improvements - allowed database modes for series moved into the individual backend drivers diff --git a/lang/de.ini b/lang/de.ini index 5b3b2dec..6ca8d049 100644 --- a/lang/de.ini +++ b/lang/de.ini @@ -257,6 +257,10 @@ rain = "Regen" sleet = "Graupel" snow = "Schnee" +[visualcrossing] +lang = "de" +units = "metric" + [phone] phonelist = "Telefonliste" incoming = "Eingehend" diff --git a/lang/en-gb.ini b/lang/en-gb.ini index d32b851e..a196b577 100644 --- a/lang/en-gb.ini +++ b/lang/en-gb.ini @@ -30,3 +30,7 @@ units = "uk2" [openweathermap] units = "hybrid" + +[visualcrossing] +lang = "en" +units = "uk" diff --git a/lang/en.ini b/lang/en.ini index 6cb5c00e..8ff4f0cb 100644 --- a/lang/en.ini +++ b/lang/en.ini @@ -254,6 +254,10 @@ units = "e" lang = "en" units = "us" +[visualcrossing] +lang = "en" +units = "us" + [phone] phonelist = "Call list" incoming = "incoming" diff --git a/lang/fr.ini b/lang/fr.ini index 43b6523e..635eae88 100644 --- a/lang/fr.ini +++ b/lang/fr.ini @@ -261,6 +261,10 @@ rain = "pluie" sleet = "neige fondue" snow = "neige" +[visualcrossing] +lang = "fr" +units = "metric" + [phone] phonelist = "Liste des coups de fil" unknown = "inconnu" diff --git a/lang/it.ini b/lang/it.ini index b4710a0c..8a50a9d8 100644 --- a/lang/it.ini +++ b/lang/it.ini @@ -257,6 +257,10 @@ rain = "pioggia" sleet = "nevischio" snow = "nevicata" +[visualcrossing] +lang = "it" +units = "metric" + [phone] phonelist = "Registro chiamate" incoming = "in arrivo" diff --git a/lang/nl.ini b/lang/nl.ini index 90a6d619..bc9a6516 100644 --- a/lang/nl.ini +++ b/lang/nl.ini @@ -237,6 +237,10 @@ rain = "regenval" sleet = "natte sneeuw" snow = "sneeuw" +[visualcrossing] +lang = "nl" +units = "metric" + [phone] unknown = "onbekend" incoming = "inkomend" diff --git a/lib/weather/service/visualcrossing.com.md b/lib/weather/service/visualcrossing.com.md new file mode 100644 index 00000000..5928c9f2 --- /dev/null +++ b/lib/weather/service/visualcrossing.com.md @@ -0,0 +1,15 @@ +# VisualCrossing.com weather service for smartVISU + +## Installation +1. register at visualcrossing.com to get an api key +2. in SmartVISU config page 1. choose visualcrossing.com, 2. enter your location 3. enter your api key + Location can be entered in the following ways: + * use latitude and longitude of your location, separated by a comma e.g. `50.93331,6.95` + * the city name can be used, too, e.g. "Würzburg" or "Würzburg, Bayern, Deutschland" + +## Troubleshooting/Debug: +1. check for file smartVISU/temp/visualcrossing_YOURLOCATION.json + this is the api response from visualcrossing.com after you have called the service once. + View it with a json viewer (normally integrated in the browser) +2. debug the service by calling YOURSERVER/smartVISU/lib/weather/service/visualcrossing.com.php?debug=1 +3. check for 'visualcrossing.com' entries in var/log/nginx/error.log (or /var/log/apache2/) diff --git a/lib/weather/service/visualcrossing.com.php b/lib/weather/service/visualcrossing.com.php new file mode 100644 index 00000000..26902d76 --- /dev/null +++ b/lib/weather/service/visualcrossing.com.php @@ -0,0 +1,141 @@ +location . '.json'); + + if ($cache->hit($this->cache_duration_minutes)) { + $content = $cache->read(); + } else { + $loadError = ''; + $url ='https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/'.$this->location.'?lang='.trans('visualcrossing', 'lang').'&unitGroup='.$units.'&include=days,current&key='.config_weather_key.'&contentType=json'; + $content = file_get_contents($url); + + if (substr($this->errorMessage, 0, 17) != 'file_get_contents') + $cache->write($content); + else { + $loadError = substr(strrchr($this->errorMessage, ':'), 2); + $this->debug('loadError:' . $loadError ); + } + + } + + $parsed_json = json_decode($content); + if ($parsed_json != null && $parsed_json->{'days'}) { + $this->debug($parsed_json); + + // today + $this->data['current']['temp'] = transunit('temp', (float)$parsed_json->{'days'}[0]->{'temp'}); + + $this->data['current']['conditions'] = translate((string)$parsed_json->{'days'}[0]->{'conditions'}, 'visualcrossing'); + $this->data['current']['icon'] = $this->icon((string)$parsed_json->{'days'}[0]->{'icon'}, $this->icon_sm); + + $wind_speed = transunit('speed', (float)$parsed_json->{'days'}[0]->{'windspeed'}); + $wind_gust = transunit('speed', (float)$parsed_json->{'days'}[0]->{'windgust'}); + $wind_dir = weather::getDirection((int)$parsed_json->{'days'}[0]->{'winddir'}); + + $this->data['current']['wind'] = translate('wind', 'weather') . " " . $wind_speed; + // when there is no wind, direction is blank + if ($parsed_json->{'days'}[0]->{'windspeed'} != 0) + $this->data['current']['wind'] .= " " . translate('from', 'weather') . " " . $wind_dir; + if ($wind_gust > 0) + $this->data['current']['wind'] .= ", " . translate('wind_gust', 'weather') . " " . $wind_gust; + + $this->data['current']['more'] = translate('humidity', 'weather') . " " . transunit('%', 100 * (float)$parsed_json->{'days'}[0]->{'humidity'}); + $this->data['current']['misc'] = translate('air pressure', 'weather') . " " . $parsed_json->{'days'}[0]->{'pressure'}.' hPa'; + + // forecast + $i = 0; + foreach ($parsed_json->{'days'} as $day) { + if ((int)$day->{'datetimeEpoch'} < mktime(0, 0, 0) || (int)$day->{'datetimeEpoch'} > time() + 3 * 24 * 60 * 60) // next 4 days only + continue; + + $this->data['forecast'][$i]['date'] = date('Y-m-d', (int)$day->{'datetimeEpoch'}); + $this->data['forecast'][$i]['conditions'] = (string)$day->{'conditions'}; + $this->data['forecast'][$i]['icon'] = $this->icon((string)$day->{'icon'}); + + $this->data['forecast'][$i]['temp'] = round((float)$day->{'tempmax'}, 0) . '°/' . round((float)$day->{'tempmin'}, 0) . '°'; + + $i++; + } + if( preg_match('/[A-Za-z]/', $this->location)) + $this->data['city'] = $this->location; + else { + $location = explode(',',$this->location); + $this->data['city'] = getLocation($location[0],$location[1]); + } + + } else { + $add= ''; + if ($loadError != '') + $add = $loadError; + $this->error('Weather: visualcCrossing.com', 'Read request failed'.($add ? ' with message:
    '.$add : '!')); + } + } + + /* + * Icon-Mapper + */ + function icon($name, $sm = 'sun_') + { + $ret = ''; + + $icon['clear-day'] = $sm . '1'; + $icon['clear-night'] = $sm . '1'; + $icon['partly-cloudy-day'] = $sm . '4'; + $icon['partly-cloudy-night'] = $sm . '4'; + $icon['fog'] = $sm . '6'; + $icon['rain'] = 'cloud_8'; + $icon['wind'] = $sm . '10'; + $icon['snow'] = $sm . '12'; + + $icon['cloudy'] = 'cloud_4'; + $icon['sleet'] = 'cloud_11'; + + $ret = $icon[$name]; + + return $ret; + } + +} + + +// ----------------------------------------------------------------------------- +// call the service +// ----------------------------------------------------------------------------- + +$service = new weather_visualcrossing(array_merge($_GET, $_POST)); +echo $service->json(); + +?> From 54e121c0d914d625c4e6b232696fd6ea18bff9d0 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Fri, 17 May 2024 16:27:32 +0200 Subject: [PATCH 056/112] allow button-styled switch in device.dimmer --- changelog.md | 2 + pages/base/base.css | 50 +++++++++++++++++---- pages/docu/device/widget_device.dimmer.html | 10 ++++- widgets/device.html | 10 +++-- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index b18170d9..1073cf2d 100755 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ - basic.print can be used to generate a tooltip on a linked widget (parameter href = widget-id, rel = 'tooltip') - additional color and indicator options for basic.trigger - plot.sparkline can now open links and popups. Tooltip shows parameter "id" as curve name (label) +- device.dimmer extended with an option "type" to display the switch as button (midi, mini, micro, icon) ### Other New Features - improved spline display for the starting point in device.uzsugraph @@ -15,6 +16,7 @@ - new weatherservice open-meteo.com - ioBroker driver now supports subscribing and unsubscribing series for individual plots which enables usage of plot.timeshift with ioBroker (thanks to YellowFlash for testing!) - new weather service VisualCrossing +- improved vertical alignment of slider track in device.dimmer when no text is configured ### Improvements - allowed database modes for series moved into the individual backend drivers diff --git a/pages/base/base.css b/pages/base/base.css index 786bc81e..7e0e31aa 100644 --- a/pages/base/base.css +++ b/pages/base/base.css @@ -1274,17 +1274,31 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { } .dimmer .switch, -.dimmer-left .switch { +.dimmer-left .switch, +.dimmer-left .ui-midi, +.dimmer-left .ui-mini, +.dimmer-left .ui-micro { float: left; padding-left: 0; margin-right: 12px; } -.dimmer-right .switch { +.dimmer-right .switch, +.dimmer-right .ui-midi, +.dimmer-right .ui-mini, +.dimmer-right .ui-micro { float: right; padding-right: 0; margin-left: 12px; } +.dimmer-left .ui-btn.ui-midi, +.dimmer-right .ui-btn.ui-midi { + margin-top: -5px; +} +.dimmer-left .ui-btn.ui-micro, +.dimmer-right .ui-btn.ui-micro { + margin-top: 10px; +} .dimmer .icon, .dimmer-left .icon, @@ -1294,11 +1308,7 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { } .dimmer p, -.dimmer-left p { - margin: 0; - padding: 0 0 5px 15px; -} - +.dimmer-left p, .dimmer-right p { margin: 0; padding: 0 0 5px 15px; @@ -1307,11 +1317,35 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { .dimmer .ui-slider, .dimmer-left .ui-slider { margin-left: 45px; - } + .dimmer-right .ui-slider { margin-right: 45px; } +.dimmer-left.ui-midi .ui-slider { + margin-left: 64px; +} +.dimmer-right.ui-midi .ui-slider { + margin-right: 64px; +} +.dimmer-left.ui-mini .ui-slider { + margin-left: 48px; +} +.dimmer-right.ui-mini .ui-slider { + margin-right: 48px; +} +.dimmer-left.ui-micro .ui-slider { + margin-left: 38px; +} +.dimmer-right.ui-micro .ui-slider { + margin-right: 38px; +} +.dimmer-notext .ui-slider { + margin-top: 4px; +} +.dimmer-notext.ui-micro .ui-slider { + margin-top: 8px; +} .codepad { display: inline-block; diff --git a/pages/docu/device/widget_device.dimmer.html b/pages/docu/device/widget_device.dimmer.html index fdd7d4a5..565e5274 100755 --- a/pages/docu/device/widget_device.dimmer.html +++ b/pages/docu/device/widget_device.dimmer.html @@ -32,5 +32,13 @@
    Example
    {{ device.dimmer('', '', 'bath.light.switch', 'bath.light.value', 20, 100, 10, icon.light('', '','bath.light.value',20,100), icon.light(), 'green', '#ff0000', 'handle', 0 ) }}
    - + + Dimmer with a button-styled switch +
    + {{ device.dimmer('', 'Dimmer', 'bath.light.switch', 'bath.light.value', '', '', '', '', '', '', '', '', '', '', 'left', 0, 'mini') }} +
    +
    + {{ device.dimmer('', 'Dimmer', 'bath.light.switch', 'bath.light.value', '', '', '', '', '', '', '', '', '', '', 'left', 0, 'mini')}} +
    + {% endblock %} diff --git a/widgets/device.html b/widgets/device.html index 353d75b2..5f3d616c 100755 --- a/widgets/device.html +++ b/widgets/device.html @@ -123,14 +123,18 @@ * @param {value=} the maximum value to display if the slider is moved to total right if this should differ from sent/received value (optional, default like max) * @param {text(left,right)=left} position of the switch: left (default) or right * @param {value=1} 'live mode': if enabled, values will be sent during sliding. '0' sends values only when sliding is stopped, after click into track or if value is edited in input field. (optional, default = 1) +* @param {type=icon} icon or button type. Valid types: 'micro', 'mini', 'midi', 'icon' (optional, default: icon) */ -{% macro dimmer(id, txt, item_switch, item_value, min, max, step, pic_on, pic_off, color_on, color_off, value_display, min_display, max_display, picpos, live) %} +{% macro dimmer(id, txt, item_switch, item_value, min, max, step, pic_on, pic_off, color_on, color_off, value_display, min_display, max_display, picpos, live, type) %} {% import "@widgets/basic.html" as basic %} - {% if picpos is empty or picpos=='left' %}
    - {{ basic.stateswitch('', item_switch, 'icon', '', [ pic_off|default('light_light.svg'), pic_on|default('light_light.svg') ], '', [ color_off , color_on|default('icon1') ]) }} + {{ basic.stateswitch('', item_switch, type|default('icon'), '', [ pic_off|default('light_light.svg'), pic_on|default('light_light.svg') ], '', [ color_off , color_on|default('icon1') ]) }}

    {{ txt|e }}

    {{ basic.slider('', item_value, min, max, step, 'horizontal', value_display|default('none'), min_display, max_display, live) }}
    From 8c8482fa47ca6b5daff87a638fe4f4d2fbef0abe Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sat, 18 May 2024 12:47:58 +0200 Subject: [PATCH 057/112] fix dimmer classes --- pages/base/base.css | 14 +++++++------- widgets/device.html | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pages/base/base.css b/pages/base/base.css index 7e0e31aa..cf8b16dd 100644 --- a/pages/base/base.css +++ b/pages/base/base.css @@ -1322,28 +1322,28 @@ input.ui-slider-input.ui-slider-no-input[data-orientation='semicircle'] { .dimmer-right .ui-slider { margin-right: 45px; } -.dimmer-left.ui-midi .ui-slider { +.dimmer-left.dimmer-midi .ui-slider { margin-left: 64px; } -.dimmer-right.ui-midi .ui-slider { +.dimmer-right.dimmer-midi .ui-slider { margin-right: 64px; } -.dimmer-left.ui-mini .ui-slider { +.dimmer-left.dimmer-mini .ui-slider { margin-left: 48px; } -.dimmer-right.ui-mini .ui-slider { +.dimmer-right.dimmer-mini .ui-slider { margin-right: 48px; } -.dimmer-left.ui-micro .ui-slider { +.dimmer-left.dimmer-micro .ui-slider { margin-left: 38px; } -.dimmer-right.ui-micro .ui-slider { +.dimmer-right.dimmer-micro .ui-slider { margin-right: 38px; } .dimmer-notext .ui-slider { margin-top: 4px; } -.dimmer-notext.ui-micro .ui-slider { +.dimmer-notext.dimmer-micro .ui-slider { margin-top: 8px; } diff --git a/widgets/device.html b/widgets/device.html index 5f3d616c..e682a4fe 100755 --- a/widgets/device.html +++ b/widgets/device.html @@ -131,7 +131,7 @@
    {{ basic.stateswitch('', item_switch, type|default('icon'), '', [ pic_off|default('light_light.svg'), pic_on|default('light_light.svg') ], '', [ color_off , color_on|default('icon1') ]) }} From 9e807355daf94de6c68ca816f41034c62ad932a8 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Sun, 19 May 2024 22:05:25 +0200 Subject: [PATCH 058/112] improve weather.current and fix humidity --- lib/weather/service/visualcrossing.com.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/weather/service/visualcrossing.com.php b/lib/weather/service/visualcrossing.com.php index 26902d76..485083bc 100644 --- a/lib/weather/service/visualcrossing.com.php +++ b/lib/weather/service/visualcrossing.com.php @@ -56,24 +56,24 @@ public function run() $this->debug($parsed_json); // today - $this->data['current']['temp'] = transunit('temp', (float)$parsed_json->{'days'}[0]->{'temp'}); + $this->data['current']['temp'] = transunit('temp', (float)$parsed_json->{'currentConditions'}->{'temp'}); - $this->data['current']['conditions'] = translate((string)$parsed_json->{'days'}[0]->{'conditions'}, 'visualcrossing'); - $this->data['current']['icon'] = $this->icon((string)$parsed_json->{'days'}[0]->{'icon'}, $this->icon_sm); + $this->data['current']['conditions'] = translate((string)$parsed_json->{'currentConditions'}->{'conditions'}, 'visualcrossing'); + $this->data['current']['icon'] = $this->icon((string)$parsed_json->{'currentConditions'}->{'icon'}, $this->icon_sm); - $wind_speed = transunit('speed', (float)$parsed_json->{'days'}[0]->{'windspeed'}); - $wind_gust = transunit('speed', (float)$parsed_json->{'days'}[0]->{'windgust'}); - $wind_dir = weather::getDirection((int)$parsed_json->{'days'}[0]->{'winddir'}); + $wind_speed = transunit('speed', (float)$parsed_json->{'currentConditions'}->{'windspeed'}); + $wind_gust = transunit('speed', (float)$parsed_json->{'currentConditions'}->{'windgust'}); + $wind_dir = weather::getDirection((int)$parsed_json->{'currentConditions'}->{'winddir'}); $this->data['current']['wind'] = translate('wind', 'weather') . " " . $wind_speed; // when there is no wind, direction is blank - if ($parsed_json->{'days'}[0]->{'windspeed'} != 0) + if ($parsed_json->{'currentConditions'}->{'windspeed'} != 0) $this->data['current']['wind'] .= " " . translate('from', 'weather') . " " . $wind_dir; if ($wind_gust > 0) $this->data['current']['wind'] .= ", " . translate('wind_gust', 'weather') . " " . $wind_gust; - $this->data['current']['more'] = translate('humidity', 'weather') . " " . transunit('%', 100 * (float)$parsed_json->{'days'}[0]->{'humidity'}); - $this->data['current']['misc'] = translate('air pressure', 'weather') . " " . $parsed_json->{'days'}[0]->{'pressure'}.' hPa'; + $this->data['current']['more'] = translate('humidity', 'weather') . " " . transunit('%', (float)$parsed_json->{'currentConditions'}->{'humidity'}); + $this->data['current']['misc'] = translate('air pressure', 'weather') . " " . $parsed_json->{'currentConditions'}->{'pressure'}.' hPa'; // forecast $i = 0; From 856a008e6b04bedc7971b735ea2f7e76270b1edd Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Mon, 20 May 2024 18:17:45 +0200 Subject: [PATCH 059/112] provide item properties in ioBroker driver --- changelog.md | 1 + driver/io_iobroker.js | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 1073cf2d..0b804b81 100755 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ - ioBroker driver now supports subscribing and unsubscribing series for individual plots which enables usage of plot.timeshift with ioBroker (thanks to YellowFlash for testing!) - new weather service VisualCrossing - improved vertical alignment of slider track in device.dimmer when no text is configured +- ioBroker driver is now able to provide item properties from the "state" object using the keyword "property", e.g. "myItem.property.lc" to get the "last changed" property for that item ### Improvements - allowed database modes for series moved into the individual backend drivers diff --git a/driver/io_iobroker.js b/driver/io_iobroker.js index ca032a95..798e1b53 100644 --- a/driver/io_iobroker.js +++ b/driver/io_iobroker.js @@ -1,7 +1,7 @@ /** * ----------------------------------------------------------------------------- * @package smartVISU - * @author Stefan Widmer (inspired by https://github.com/ioBroker/ioBroker.socketio/blob/master/example/conn.js) + * @author Stefan Widmer (inspired by https://github.com/ioBroker/ioBroker.socketio/blob/master/example/conn.js), Wolfram v. Hülsen * @copyright 2017 - 2024 * @license GPL [http://www.gnu.de] * ----------------------------------------------------------------------------- @@ -192,6 +192,12 @@ var io = { }, listeners: [], + /** + * array of items where a property is requested on the page with keyword "property": + * .property. e.g. kitchen.light.property.lc + * if any property is requested for an item the driver maps all properties for that item + */ + properties: [], /** * supported aggregate functions in the backends database @@ -356,14 +362,23 @@ var io = { monitor: function() { io.monitorCompleted = false; io.listeners = []; + io.properties = []; var listeners = widget.listeners(); var listenItem; var listenItemEnd; for (var i=0; i < listeners.length; i++){ listenItemEnd = listeners[i].indexOf(':'); listenItem = (listenItemEnd == -1 ? listeners[i] : listeners[i].substring(0, listenItemEnd)); + + if (listenItem.indexOf('.property') != -1){ + listenItemEnd = listenItem.indexOf('.property'); + listenItem = listenItem.substring(0, listenItemEnd); + if (!io.properties.includes(listenItem)) + io.properties.push(listenItem); + } + if ( io.listeners[listenItem] == undefined || listenItem == io.listeners[listenItem]) - io.listeners[listenItem] = listeners[i]; + io.listeners[listenItem] = listeners[i].indexOf('.property') == -1 ? listeners[i] : listenItem; } var items = Object.keys(io.listeners); io.openItems = Object.keys(io.listeners); @@ -441,6 +456,12 @@ var io = { io.openItems.removeEntry(item); if (item != io.listeners[item]) widget.update(io.listeners[item], val); + if (io.properties.includes(item)){ + for (var key in state){ + if (state.hasOwnProperty(key) && key != 'val') + widget.update(item + '.property.' + key, state[key] ); + } + } } }, From c5fd8de573e1264ed06e3aa13fb0396c73679db8 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Mon, 20 May 2024 18:32:52 +0200 Subject: [PATCH 060/112] improve dimmer docu --- pages/docu/device/widget_device.dimmer.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pages/docu/device/widget_device.dimmer.html b/pages/docu/device/widget_device.dimmer.html index 565e5274..944690ab 100755 --- a/pages/docu/device/widget_device.dimmer.html +++ b/pages/docu/device/widget_device.dimmer.html @@ -40,5 +40,16 @@
    Example
    {{ device.dimmer('', 'Dimmer', 'bath.light.switch', 'bath.light.value', '', '', '', '', '', '', '', '', '', '', 'left', 0, 'mini')}}
    + + While the dimmer text is normally aligned left it is centered if the dimmer resides in a block. To align it left again, use the following code in your visu.css on a dimmer with icon on the left side: +
    + + .block .dimmer-left p {
    + text-align: left;
    + } +
    +
    + Use the class ".dimmer-right" for dimmers with icon on the right side. A margin definition can be added for additional fine-positioning of the text. + {% endblock %} From fbfc6464037e2b9956b770cd6a455239c39b6cb5 Mon Sep 17 00:00:00 2001 From: wvhn <17801971+wvhn@users.noreply.github.com> Date: Tue, 21 May 2024 11:50:51 +0200 Subject: [PATCH 061/112] fix uzsu designtype (hidden feature) --- widgets/device.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widgets/device.html b/widgets/device.html index e682a4fe..6e30c055 100755 --- a/widgets/device.html +++ b/widgets/device.html @@ -531,7 +531,7 @@ * @param {color(icon0to5)=icon1} the color for the 'on' state, e.g. '#f00' for red (default 'icon1' of the design) * @param {color(icon0to5)=icon0} the color for the 'off' state, e.g. '#f00' for red (default 'icon0' of the design) * @param {type=icon} button sized 'micro', 'mini' or 'midi' or icon (optional, default = 'icon') -* @param {value=} identifier for the backend: 0=smarthomeNG style, 2= fhem style (not necessary, default is derived from io driver) +* @param {value=} identifier for the backend: 0=smarthomeNG style, 2= fhem style (not necessary, default is derived from io driver, parameter is hidden in docu) * * @author Michael Wuertenberger */ @@ -546,7 +546,7 @@ {% endif %} From 3cbdc077b3c2e976c6f125373280f1d76845a90d Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Fri, 31 May 2024 22:26:08 +0200 Subject: [PATCH 062/112] quad design: add indicator option to color, shutter, playercontrol, blind widgets --- widgets/quad.html | 90 ++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/widgets/quad.html b/widgets/quad.html index 5d87ef3b..585b27c5 100755 --- a/widgets/quad.html +++ b/widgets/quad.html @@ -733,8 +733,12 @@ possible elements are: 'colorpicker', 'ww_popup', 'ww_slider', 'values', 'sequencer', 'locks', 'stateengine', 'uzsu', 'plot' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) Combine elements in one column by putting them in arrays. Standard is [['locks', 'sequencer', 'colorpicker'],'values', 'stateengine', 'plot', 'uzsu'] + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. + */ -{% macro color(id, item_value_r, item_value_g, item_value_b, min, max, step, colors, style, colormodel, linetext, item_switch_r, item_switch_g, item_switch_b, min_display, max_display, item_uzsu, uzsu_attribs, popupcolor, item_switch_ww, item_value_ww, item_plot, icon_plot, item_auto, extpopup, locks, item_seq, icon_color, linetext_widget, live, column_order) %} +{% macro color(id, item_value_r, item_value_g, item_value_b, min, max, step, colors, style, colormodel, linetext, item_switch_r, item_switch_g, item_switch_b, min_display, max_display, item_uzsu, uzsu_attribs, popupcolor, item_switch_ww, item_value_ww, item_plot, icon_plot, item_auto, extpopup, locks, item_seq, icon_color, linetext_widget, live, column_order, indicator) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/plot.html" as plot %} {% import "@widgets/icon.html" as icon %} @@ -1184,7 +1188,9 @@ [slider_item, slider_min, slider_max, slider_step, '', 'handle']] * @param {widget[?]=} Widget(s) to be shown right after linetext. Can be used to show a countdown or other additional information. Example: basic.symbol('', 'licht.og.essen.sa') - don't put basic.symbol() in high commas! (optional) - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text[](move_up,move_down,stop,pos_slider,pos_shutter,pos_shutter_ext,pos_popup_shutter,pos_popup_blind,pos1,pos2,stateengine,uzsu,plot,extpopup,anynumber)=} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) possible elements are: 'move_down', 'move_up', 'stop', 'pos_slider', 'pos_shutter', 'pos_shutter_ext' (with the two saved positions), 'pos_popup_shutter', 'pos_popup_blind', 'pos1', 'pos2', 'stateengine', 'uzsu', 'plot', 'extpopup' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) @@ -1193,7 +1199,7 @@ * @param {unspecified[]=} a list with positional attributes. Should be left untouched as it is set automatically (optional) */ -{% macro shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, item_saved, min, max, step, mode, background, value_pos_1, value_pos_2, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, place4, column_order, _blind_or_shutter, _pos_attribs) %} +{% macro shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, item_saved, min, max, step, mode, background, value_pos_1, value_pos_2, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, indicator, column_order, _blind_or_shutter, _pos_attribs) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/device.html" as device %} {% import "@widgets/icon.html" as icon %} @@ -1416,9 +1422,9 @@ {% if column == 'move_down' and item_move %} {% if move_on_longpress is same as true or move_on_longpress == 'true' or move_on_longpress is empty %} - {{ basic.stateswitch(id~'_down', item_stop, '', max < min ? 0 : 1, 'control_arrow_down', '', '', 'blink', item_move, max < min ? 0 : 1) }} + {{ basic.stateswitch(id~'_down', item_stop, '', max < min ? 0 : 1, 'control_arrow_down', '', '', indicator, item_move, max < min ? 0 : 1) }} {% else %} - {{ basic.stateswitch(id~'_down', item_move, '', max < min ? 0 : 1, 'control_arrow_down', '', '', 'blink') }} + {{ basic.stateswitch(id~'_down', item_move, '', max < min ? 0 : 1, 'control_arrow_down', '', '', indicator) }} {% endif %} {% if elements[loop.index] == 'div' %}
    @@ -1427,9 +1433,9 @@ {% if column == 'move_up' and item_move %} {% if move_on_longpress is same as true or move_on_longpress == 'true' or move_on_longpress is empty %} - {{ basic.stateswitch(id~'_up', item_stop, '', max < min ? 1 : 0, 'control_arrow_up', '', '', 'blink', item_move, max < min ? 1 : 0) }} + {{ basic.stateswitch(id~'_up', item_stop, '', max < min ? 1 : 0, 'control_arrow_up', '', '', indicator, item_move, max < min ? 1 : 0) }} {% else %} - {{ basic.stateswitch(id~'_up', item_move, '', max < min ? 1 : 0, 'control_arrow_up', '', '', 'blink') }} + {{ basic.stateswitch(id~'_up', item_move, '', max < min ? 1 : 0, 'control_arrow_up', '', '', indicator) }} {% endif %} {% if elements[loop.index] == 'div' %}
    @@ -1437,7 +1443,7 @@ {% endif %} {% if column == 'stop' and item_stop %} - {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '', 'control_cancel', '', 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '', 'control_cancel', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %}
    {% endif %} @@ -1487,7 +1493,7 @@ {% if item_saved %}
    - {{ basic.stateswitch(id~'_saved1_ext', item_saved, '', value_pos_1, '', text_pos_1, 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved1_ext', item_saved, '', value_pos_1, '', text_pos_1, 'icon0', indicator) }}  
    {% endif %} @@ -1496,7 +1502,7 @@ {% if item_saved %}
    - {{ basic.stateswitch(id~'_saved2_ext', item_saved, '', value_pos_2, '', text_pos_2, 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved2_ext', item_saved, '', value_pos_2, '', text_pos_2, 'icon0', indicator) }}  
    {% endif %} @@ -1508,19 +1514,19 @@ {% endif %} {% if column == 'pos' and item_saved and value_pos_2 and value_pos_1 %} - {{ basic.select(id~'pos', item_saved, 'mini', [value_pos_1, value_pos_2, 0], '', [text_pos_1, text_pos_2, 0], 'icon1', 'horizontal') }} + {{ basic.select(id~'pos', item_saved, 'mini', [value_pos_1, value_pos_2, 0], '', [text_pos_1, text_pos_2, 0], 'icon1', 'horizontal', '', '', indicator) }} {% if elements[loop.index] == 'div' %}
    {% endif %} {% elseif column == 'pos1' and item_saved and value_pos_1 and 'pos' not in elements %} - {{ basic.stateswitch(id~'_saved1', item_saved, '', [value_pos_1, 0], '', [0, text_pos_1], 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved1', item_saved, '', [value_pos_1, 0], '', [0, text_pos_1], 'icon0', indicator) }} {% if elements[loop.index] == 'div' %}
    {% endif %} {% elseif column == 'pos2' and item_saved and value_pos_2 and 'pos' not in elements %} - {{ basic.stateswitch(id~'_saved2', item_saved, '', [value_pos_2, 0], '', [0, text_pos_2], 'icon0', 'blink') }} + {{ basic.stateswitch(id~'_saved2', item_saved, '', [value_pos_2, 0], '', [0, text_pos_2], 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -1639,16 +1645,18 @@ * @param {unspecified[]=} a list with positional attributes: item_saved = an item for some saved positions, value_pos_1 = the value to send for position 1, value_pos_2 = the value to send for position 2, text_pos_1 = the text printed on button for position 1, text_pos_2 = the text printed on button for position 2 (optional) * @param {widget[?]=} Widget(s) to be shown right after linetext. Can be used to show a countdown or other additional information. Example: basic.symbol('', 'licht.og.essen.sa') - don't put basic.symbol() in high commas! (optional) - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text[](move_up,move_down,stop,pos_slider,pos_shutter,pos_shutter_ext,pos_popup_shutter,pos_popup_blind,pos1,pos2,stateengine,uzsu,plot,extpopup,anynumber)=[[move_down, move_up], pos_popup_shutter, stateengine, plot, uzsu]} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) possible elements are: 'move_down', 'move_up', 'stop', 'pos_slider', 'pos_shutter', 'pos_shutter_ext' (with the two saved positions), 'pos_popup_shutter', 'pos_popup_blind', 'pos1', 'pos2', 'stateengine', 'uzsu', 'plot', 'extpopup' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) Combine elements in one column by putting them in arrays. Standard is [['move_down', 'move_up'],'pos_popup_shutter', 'stateengine', 'plot', 'uzsu'] */ -{% macro blind(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, min, max, step, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, pos_attribs, linetext_widget, place4, column_order) %} +{% macro blind(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, min, max, step, move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, pos_attribs, linetext_widget, indicator, column_order) %} {% import _self as newwidget %} - {{ newwidget.shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, '', min, max, step, '', '', '', '', move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, place4, column_order, 'blind', pos_attribs) }} + {{ newwidget.shutter(id, linetext, item_move, item_stop, item_pos, item_shift, item_angle, '', min, max, step, '', '', '', '', move_on_longpress, live, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, linetext_widget, indicator, column_order, 'blind', pos_attribs) }} {% endmacro %} /** @@ -1697,14 +1705,16 @@ [slider_item, slider_min, slider_max, slider_step, '', 'handle']] * @param {text=} the path/url or item to the image. For squeezebox create an item with the following value: 'http://IP:PORT/music/current/cover.jpg?player=MACADDRESS' * @param {value=1} 'live mode': if enabled, values will be sent during sliding. '0' sends values only when sliding is stopped, after click into track or if value is edited in input field. (optional, default = 1) - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text[](previous,play,pause,stop,next,power,eject,mute,volume_slider,cover,volume_popup,volume_up,volume_down,song_position,uzsu,plot,stateengine,repeat,source,source_popup,speaker,playpause,playpausestop,playstop,shuffle,anynumber)=} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) possible elements are: 'previous', 'play', 'pause', 'stop', 'next', 'power', 'eject', 'mute', 'volume_slider', 'cover', 'volume_popup', 'volume_up', 'volume_down', 'song_position', 'uzsu', 'plot', 'stateengine', 'repeat', 'source', 'speaker', 'playpause', 'playstop', 'playpausestop', 'shuffle', 'source_popup' For empty columns either use ' ' or a number to define the column width (e.g. '40' = 40 pixels width) Combine elements in one column by putting them in arrays. */ -{% macro playercontrol(id, linetext, item_previous, item_play, item_pause, item_stop, item_next, item_power, item_eject, item_repeat, item_shuffle, source, item_mute, item_volume, item_volumeup, item_volumedown, volume_min, volume_max, volume_step, volume_min_display, volume_max_display, volume_threshold, item_album, item_artist, item_title, item_time, item_duration, item_speaker, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, coverUrl, live, place2, column_order) %} +{% macro playercontrol(id, linetext, item_previous, item_play, item_pause, item_stop, item_next, item_power, item_eject, item_repeat, item_shuffle, source, item_mute, item_volume, item_volumeup, item_volumedown, volume_min, volume_max, volume_step, volume_min_display, volume_max_display, volume_threshold, item_album, item_artist, item_title, item_time, item_duration, item_speaker, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, coverUrl, live, indicator, column_order) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/plot.html" as plot %} {% import "@widgets/device.html" as device %} @@ -1971,14 +1981,14 @@
    {% endif %} {% if column == 'power' and item_power %} - {{ basic.stateswitch(id~'_power', item_power, 'icon', [0,1], '', '', ['icon0','icon1'], 'blink') }} + {{ basic.stateswitch(id~'_power', item_power, 'icon', [0,1], '', '', ['icon0','icon1'], indicator) }} {% if elements[loop.index] == 'div' %}
    {% endif %} {% endif %} {% if column == 'eject' and item_eject %} - {{ basic.stateswitch(id~'_eject', item_eject, 'icon', '1', 'audio_eject', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_eject', item_eject, 'icon', '1', 'audio_eject', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2003,34 +2013,34 @@ {% endif %} {% if column == 'previous' and item_previous %} - {{ basic.stateswitch(id~'_prev', item_previous, 'icon', '1', 'audio_previous', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_prev', item_previous, 'icon', '1', 'audio_previous', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'next' and item_next %} - {{ basic.stateswitch(id~'_prev', item_next, 'icon', '1', 'audio_next', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_prev', item_next, 'icon', '1', 'audio_next', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'play' and item_play %} - {{ basic.stateswitch(id~'_play', item_play, 'icon', '1', 'audio_play', '', 'icon1', '') }} + {{ basic.stateswitch(id~'_play', item_play, 'icon', '1', 'audio_play', '', 'icon1', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'pause' and item_pause %} - {{ basic.stateswitch(id~'_pause', item_pause, 'icon', '1', 'audio_pause', '', 'icon1', 'blink') }} + {{ basic.stateswitch(id~'_pause', item_pause, 'icon', '1', 'audio_pause', '', 'icon1', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'playstop' and item_play %} - {{ basic.stateswitch(id~'_playstop', item_play, 'icon', ['0','1'], ['audio_play', 'audio_stop'], '', ['icon0', 'icon1'], 'blink') }} + {{ basic.stateswitch(id~'_playstop', item_play, 'icon', ['0','1'], ['audio_play', 'audio_stop'], '', ['icon0', 'icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% elseif column == 'playpause' and item_pause %} - {{ basic.stateswitch(id~'_playpause', item_play, 'icon', ['0','1'], ['audio_play', 'audio_pause'], '', ['icon0', 'icon1'], 'blink') }} + {{ basic.stateswitch(id~'_playpause', item_play, 'icon', ['0','1'], ['audio_play', 'audio_pause'], '', ['icon0', 'icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2041,25 +2051,25 @@ {% endif %} {% endif %} {% if column == 'stop' and item_stop %} - {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '1', 'audio_stop', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_stop', item_stop, 'icon', '1', 'audio_stop', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'mute' and item_mute %} - {{ basic.stateswitch(id~'_mute', item_mute, 'icon', [0,1], 'audio_volume_mute', '', '', '') }} + {{ basic.stateswitch(id~'_mute', item_mute, 'icon', [0,1], 'audio_volume_mute', '', '', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'volume_up' and item_volumeup %} - {{ basic.stateswitch(id~'_volup', item_volumeup, 'icon', '1', 'audio_volume_high', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_volup', item_volumeup, 'icon', '1', 'audio_volume_high', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'volume_down' and item_volumedown %} - {{ basic.stateswitch(id~'_voldown', item_volumedown, 'icon', '1', 'audio_volume_low', '', 'icon0', '') }} + {{ basic.stateswitch(id~'_voldown', item_volumedown, 'icon', '1', 'audio_volume_low', '', 'icon0', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2087,15 +2097,15 @@ {% set text_source = text_source|merge([item[2]|default('')]) %} {% endif %} {% endfor %} - {{ basic.select(id~'_select', item_source, type_source, value_source, pic_source, text_source, 'icon1') }} + {{ basic.select(id~'_select', item_source, type_source, value_source, pic_source, text_source, 'icon1', 'horizontal', '', '', indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'speaker' and item_speaker %} - {{ basic.stateswitch(id~'_speaker1', item_speaker[0], 'mini', [1,0], '', 'A', ['icon1', 'icon0'], '') }} - {{ basic.stateswitch(id~'_speaker2', item_speaker[1], 'mini', [1,0], '', 'B', ['icon1', 'icon0'], '') }} + {{ basic.stateswitch(id~'_speaker1', item_speaker[0], 'mini', [1,0], '', 'A', ['icon1', 'icon0'], indicator) }} + {{ basic.stateswitch(id~'_speaker2', item_speaker[1], 'mini', [1,0], '', 'B', ['icon1', 'icon0'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2103,13 +2113,13 @@ {% if column == 'repeat' and item_repeat %} - {{ basic.stateswitch(id~'_repeat', item_repeat, 'icon', [0,1,2], ['audio_repeat_song','audio_repeat_song','audio_repeat'], '', ['icon0','icon1','icon1'], '') }} + {{ basic.stateswitch(id~'_repeat', item_repeat, 'icon', [0,1,2], ['audio_repeat_song','audio_repeat_song','audio_repeat'], '', ['icon0','icon1','icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} {% endif %} {% if column == 'shuffle' and item_shuffle %} - {{ basic.stateswitch(id~'_shuffle', item_shuffle, 'icon', [0,1,2], ['audio_shuffle','audio_shuffle','audio_shuffle_album'], '', ['icon0','icon1','icon1'], '') }} + {{ basic.stateswitch(id~'_shuffle', item_shuffle, 'icon', [0,1,2], ['audio_shuffle','audio_shuffle','audio_shuffle_album'], '', ['icon0','icon1','icon1'], indicator) }} {% if elements[loop.index] == 'div' %} {% endif %} @@ -2236,17 +2246,17 @@ * @param {text=} the value to send on releasing after a longpress (optional) * @param {placeholder=} placeholder attributes for future features, etc. * @param {placeholder=} placeholder attributes for future features, etc. - If you want to implement two state switches for one single item (e.g. on/off and timer or restart, etc.) - you can provide the following attributes as single statements without arrays. + If you want to implement two state switches for one single item (e.g. on/off and timer or restart, etc.) + you can provide the following attributes as single statements without arrays. * @param {text=} text for the whole line (optional) * @param {text[?]=} text for each column (optional) * @param {item[?](dict)=} a gad/item for UZSU * @param {uzsuparam[]=} Array with standard UZSU parameters: pic_on, pic_off, valueType, valueParameterList, color_on, color_off. (optional) Normally the array must have the same dimension as the "type" parameter, e.g. "[['', '', 'num'], '', ['', '', 'bool']]" for a table of 3 columns. - Exception: if the parameter "item_uzsu" is a singe item in string format a new column with an uzsu icon is displayed behind the main column block. + Exception: if the parameter "item_uzsu" is a singe item in string format a new column with an uzsu icon is displayed behind the main column block. * @param {plotparam[?]=} array with all plot.period attributes to show a plot in popup. Alternatively just the item to be plotted. Normally the array must have the same dimension as the "type" parameter, e.g. "[['plotitem1', 'avg', '2d','now'], 'plotitem2', '']" for a table of 3 columns. - Exception: a plot parameter array with exactly 17 entries causes a new column with a plot icon to be displayed behind the main column block. Example: "['plotitem1', 'avg', '2d','now', '','','','','','','','','','','','','temp'] + Exception: a plot parameter array with exactly 17 entries causes a new column with a plot icon to be displayed behind the main column block. Example: "['plotitem1', 'avg', '2d','now', '','','','','','','','','','','','','temp'] * @param {image=measure_power_meter} icon triggering the plot popup * @param {item[?](foo)=} "root item" which holds stateengine information. show current state of stateengine/stateengine item. Adjust icons and states below accordingly * @param {unspecified[]=} Array of arrays for extended popup window. Use this to create an icon to open a popup with switches, sliders, flips or select menues. @@ -2258,7 +2268,7 @@ [slider_item, slider_min, slider_max, slider_step, '', 'handle']] * @param {item[](bool,num)=} array with items for locking. You have to be aware of the order: item_lock, item_bwmlock (presence sensor), item_force (force on/off/neutral) * @param {sliderparam[?]=} additional item for changing value (e.g. timer). A slider popup will be shown - You can provide additional attributes as an array: item, min, max, step, format, value_display (handle, etc.) + You can provide additional attributes as an array: item, min, max, step, format, value_display (handle, etc.) * @param {widget[?]=} Widget(s) to be shown right after linetext. Can be used to show a countdown or other additional information. Example: basic.symbol('', 'licht.og.essen.sa') - don't put basic.symbol() in high commas! (optional) * @param {placeholder=} placeholder attributes for future features, etc. * @param {text[](switch,stateengine,uzsu,plot,locks,slider,extpopup,anynumber)=['locks', 'switch', 'slider', 'stateengine', 'plot', 'uzsu']} array with element description: Reorder elements to your liking (esp. relevant for smartphones as several columns might be too much) From b2f36ac4533ce54aac5862a6b7ad1ca427092f8d Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Fri, 31 May 2024 22:26:55 +0200 Subject: [PATCH 063/112] quad design: update stateswitch and select widgets to newest version. Remove "group" option for select widget --- widgets/quad.html | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/widgets/quad.html b/widgets/quad.html index 585b27c5..369790e7 100755 --- a/widgets/quad.html +++ b/widgets/quad.html @@ -2277,8 +2277,12 @@ Combine elements in one column by putting them in arrays. Standard is ['locks', 'switch', 'slider', 'stateengine', 'plot', 'uzsu'] Be aware that this only works with lines containing one switch only! For multiple switches leave column_order empty and rely on the defaults. * @param {text(switch,select)=} Should be left untouched as it is set automatically (optional) + * @param {item(list)=} Should be left untouched. an item containing all selectable values (optional) - overrides existing value definitions, available for select menu type only + * @param {item(list)=} Should be left untouched. an item containing the texts corresponding to the selectable values (optional even if itemvals is specified), available for select menu type only + */ -{% macro stateswitch(id, item_switch, type, value, pic, text, color, indicator, item_longpress, value_longpress, value_longrelease, place1, place2, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, linetext_widget, place4, column_order, _switch_or_select) %} + +{% macro stateswitch(id, item_switch, type, value, pic, text, color, indicator, item_longpress, value_longpress, value_longrelease, place1, place2, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, linetext_widget, place4, column_order, _switch_or_select, itemvals, itemtxts) %} {% import "@widgets/basic.html" as basic %} {% import "@widgets/device.html" as device %} {% import "@widgets/icon.html" as icon %} @@ -2547,7 +2551,7 @@ {% if column == 'switch' and item_switch[i] %} {% if columntext[i] %}{{ columntext[i]|e }}{% endif %} {% if _switch_or_select[i] == 'select' or _switch_or_select == 'select' %} - {{ basic.select(id~'_select'~z~i, item_switch[i], type[i], value[i]|default(value[i][0]), pic[i]|default(pic[i][0]), text[i]|default(text[i][0]), color_on[i]|default(''), indicator[i]) }} + {{ basic.select(id~'_select'~z~i, item_switch[i], type[i], value[i]|default(value[i][0]), pic[i]|default(pic[i][0]), text[i]|default(text[i][0]), color_on[i]|default(''), '', itemvals[i]|default(''), itemtxts[i]|default(''), indicator[i]) }} {% else %} {{ basic.stateswitch(id~'_stateswitch'~z~i, item_switch[i], type[i], value[i]|default(''), pic[i]|default(''), text[i]|default(''), color[i]|default(''), indicator[i]|default(''), item_longpress[i]|default(''), value_longpress[i]|default(''), value_longrelease[i]|default('')) }} {% endif %} @@ -2682,7 +2686,7 @@ {% if column == 'switch' and item_switch %} {% if columntext %}{{ columntext|e }}{% endif %} {% if _switch_or_select == 'select' %} - {{ basic.select(id~'_select'~z~i, item_switch, type, value|default(value[0]), pic|default(pic[0]), text|default(text[0]), color_on|default(''), indicator) }} + {{ basic.select(id~'_select'~z~i, item_switch, type, value|default(value[0]), pic|default(pic[0]), text|default(text[0]), color_on|default(''), '', itemvals|default(''), itemtxts|default(''), indicator) }} {% else %} {{ basic.stateswitch(id~'_stateswitch'~z~i, item_switch, type|default(''), value|default(''), pic|default(''), text|default(''), color|default(''), indicator|default(''), item_longpress|default(''), value_longpress|default(''), value_longrelease|default('')) }} {% endif %} @@ -2895,9 +2899,11 @@ * @param {image[?]=} list of icons for every button (optional) - not supported for type 'menu' * @param {text[?]=} list of texts for every menu entry or button (optional) * @param {color[?](icon0to5)=icon1} the color for the on state of the buttons, e.g. 'icon0'through'icon5' or '#f00' or 'red' (optional, default: icon1) - not supported for type 'menu' - * @param {text[?](horizontal,vertical,none)=horizontal} orientation of the controlgroup: 'horizontal', 'vertical' or 'none' for seperate buttons (optional, default: 'horizontal') - not supported for type 'menu' - * @param {placeholder=} placeholder attributes for future features, etc. - * @param {placeholder=} placeholder attributes for future features, etc. + * @param {item(list)=} an item containing all selectable values (optional) - overrides existing value definitions, available for menu type only + * @param {item(list)=} an item containing the texts corresponding to the selectable values (optional even if itemvals is specified), available for menu type only + * @param {color(icon0to5,blink,simulate,:)=} activity indicator which is active until response (or a timeout is reached); pass either a color, icon class, 'blink' or 'simulate'. Timeout is 3s by default and can be set globally using the configuration key "indicator_duration". Add a colon and integer number to specify an individual timeout, e.g. 'blink:15' (optional) + You can also set an indicator icon, text and color by passing these as additional parameters in the icon, text and color arrays. Just provide one icon more than you have values to switch. A dynamic icon can be used also along with the 'simulate' option. + The indicator options will not be applied to the select element of type 'menu' which will convert all options to 'blink'. * @param {text=} text for the whole line (optional) * @param {text[?]=} (array with) text for each column (optional) * @param {item[?](dict)=} a gad/item for UZSU @@ -2927,9 +2933,9 @@ Combine elements in one column by putting them in arrays. Standard is ['locks', 'select', 'stateengine', 'plot', 'uzsu'] Be aware that this only works with lines containing one switch only! For multiple switches leave column_order empty and rely on the defaults. */ -{% macro select(id, item_select, type, value, pic, text, color_on, group, place1, place2, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place3, place4, column_order) %} +{% macro select(id, item_select, type, value, pic, text, color_on, itemvals, itemtxts, indicator, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place3, place4, column_order) %} {% import _self as intern %} - {{ intern.stateswitch(id, item_select, type, value, pic, text, color_on, place0, place1, place2, place3, place4, place5, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place6, place7, column_order, 'select') }} + {{ intern.stateswitch(id, item_select, type, value, pic, text, color_on, indicator, place1, place2, place3, place4, place5, linetext, columntext, item_uzsu, uzsu_attribs, item_plot, icon_plot, item_auto, extpopup, locks, item_slider, place6, place7, column_order, 'select', itemvals, itemtxts) }} {% endmacro %} /** From 3adc59c0b435630366ad90ca912a8fd4f04fa4ff Mon Sep 17 00:00:00 2001 From: Onkel Andy Date: Fri, 31 May 2024 22:27:33 +0200 Subject: [PATCH 064/112] quad design: fix examples as group paramter is removed from select widget --- pages/example4.quad/licht_eg.html | 2 +- pages/example4.quad/licht_og.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/example4.quad/licht_eg.html b/pages/example4.quad/licht_eg.html index 5e61eebd..321826ac 100755 --- a/pages/example4.quad/licht_eg.html +++ b/pages/example4.quad/licht_eg.html @@ -41,7 +41,7 @@ |
    Lichtkurven