diff --git a/docs/source/ESPEasy/ESPEasyLogo_blue_on_white.svg b/docs/source/ESPEasy/ESPEasyLogo_blue_on_white.svg
new file mode 100755
index 000000000..64e33d0bb
--- /dev/null
+++ b/docs/source/ESPEasy/ESPEasyLogo_blue_on_white.svg
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/docs/source/ESPEasy/ESPEasyLogo_blue_on_white_min.svg b/docs/source/ESPEasy/ESPEasyLogo_blue_on_white_min.svg
new file mode 100755
index 000000000..f934864b1
--- /dev/null
+++ b/docs/source/ESPEasy/ESPEasyLogo_blue_on_white_min.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/source/ESPEasy/ESPEasyLogo_color.svg b/docs/source/ESPEasy/ESPEasyLogo_color.svg
new file mode 100755
index 000000000..1a419e054
--- /dev/null
+++ b/docs/source/ESPEasy/ESPEasyLogo_color.svg
@@ -0,0 +1,92 @@
+
+
+
+
diff --git a/docs/source/Participate/PlatformIO.rst b/docs/source/Participate/PlatformIO.rst
index eb32add3c..3b39bae42 100644
--- a/docs/source/Participate/PlatformIO.rst
+++ b/docs/source/Participate/PlatformIO.rst
@@ -260,6 +260,7 @@ If only an USB device was detected but no `/dev/ttyUSBxx` was assigned, then a m
* ``sudo modprobe ch341`` - For CH340/CH341 USB to serial chips (rectangular form chip)
* ``sudo modprobe cp210x`` - For FTDI CP210x USB to serial chips (small square form factor)
* ``sudo modprobe ftdi_sio`` - For FTDI USB to serial chips with multiple serial ports like the FTDI4232H which has 4 serial ports.
+* ``sudo modprobe vhci-hcd`` - For the virtual serial port passed from the Windows side to WSL2 (since WSL2 started using Linux 6.x core)
After calling these 'modprobe' commands, the result can be verified using ``lsmod`` and re-checking ``dmesg`` to see which ``/dev/ttyUSBxx`` ports were assigned.
diff --git a/docs/source/Plugin/DataAcquisition.repl b/docs/source/Plugin/DataAcquisition.repl
new file mode 100644
index 000000000..cc3c5e237
--- /dev/null
+++ b/docs/source/Plugin/DataAcquisition.repl
@@ -0,0 +1,23 @@
+
+Data Acquisition
+^^^^^^^^^^^^^^^^
+
+This group of settings are standard available configuration items.
+
+* **Single event with all values**: When this setting is enabled, all available values will be sent in a single event ``#All``, with all values in order as arguments to the event.
+
+* **Show derived values**: When checked, the Devices overview page, and the ``/json`` endpoint (used for updating the Devices overview page) will include any Derived values as defined. See the ``TaskValueSetDerived`` and ``TaskValueSetPresentation`` commands.
+
+* **Event & Log derived values**: When checked, the Derived values will be generated as Events, to be handled in Rules, and sent to logging devices like the Syslog server and/or SD-card logging.
+
+* **Send derived values**: When checked, the Derived values will be sent to any enabled Controllers that support extra values like MQTT and HTTP/string value controllers.
+
+(The derived values options are only available if String variables feature is included in the build.)
+
+|
+
+* **Send to Controller**: Select the Controller(s) to send the Values to, either on a ``TaskRun`` command applied to the task, or on an **Interval** time action.
+
+Send to Controller is only visible when one or more Controllers are configured.
+
+|
diff --git a/docs/source/Plugin/P000_commands.repl b/docs/source/Plugin/P000_commands.repl
index 9a6561c64..92baa550e 100644
--- a/docs/source/Plugin/P000_commands.repl
+++ b/docs/source/Plugin/P000_commands.repl
@@ -793,6 +793,102 @@
``TaskValueSetAndRun,,,``"
"
+ TaskValueSetDerived","
+ :green:`Rules`","
+ Define a Derived value for a task. A Derived value is an extra value, available for the device, that can return a calculated result, based on any variable available in the system.
+
+ ``TaskValueSetDerived,,,[,]`` (Added: 2025/05/31, only available when String Variables feature is included in the build)
+
+ ````: Either a task number or a task name, for the task to add the derived value to.
+
+ ````: A name for the derived value, numbers can not be used, only by name.
+
+ ````: The formula to calculate and return as the value for the derived value. The content of the formula should be escaped, else the rules processor will fill the current values, instead of during retrieval of the derived value. Formulas often contain spaces or commas, if so they have to be quoted. See examples.
+
+ ````: An optional Unit of Measure to be appended after the value. Must be quoted if it contains spaces or commas. (Added:2025/06/08)
+
+ **Examples:**
+
+ In ``On System#Boot Do`` you can add the definition for a derived value, here we have a BME task, where we want to add a DewPoint value and a value for degrees Fahrenheit. All symbols used for addressing conversions, functions and variables, like ``%``, ``{``, ``}``, ``[`` and ``]`` are escaped by using a ``\``.
+
+ ``TaskValueSetDerived,BME,DewPoint,'\%c_dew_th\%(\[BME#temperature\],\[BME#humidity\])'``
+
+ ``TaskValueSetDerived,BME,Fahrenheit,'\%c_c2f\%(\[BME#temperature\])'``
+
+ In other places, like rules, and in Display tasks, you can now use ``[BME#DewPoint]`` and ``[BME#Fahrenheit]`` to retrieve the freshly calculated result of the formulas. NB: If the value doesn't get updated, most likely the formula wasn't escaped properly, and the *calculate result* stored in memory, instead of the formula.
+
+ Derived values can be shown on the Devices overview page, and included in the ``/json`` output, by enabling the **Show derived values** checkbox in the device configuration.
+ "
+ "
+ TaskValueSetPresentation","
+ :green:`Rules`","
+ Define a presentation formula for a task. The presentation result will be used when displaying the devices values on the Devices overview page, and as the ``Value`` in the ``/json`` output, as that's used for refreshing the Devices page.
+
+ ``TaskValueSetPresentation,,,`` (Added: 2025/05/31, only available when String Variables feature is included in the build)
+
+ ````: Either a task number or a task name, for the task to add the presentation formula to.
+
+ ````: The name for the value to apply the presentation on, numbers can not be used, only by name. Existing plugin value names and Derived value names can be used.
+
+ ````: The formula to calculate and return as the display for the addressed value. Use the ``%value%`` placeholder to insert the current Value, this can be used multiple times if needed. The content of the formula should probably be escaped, as the rules processor might fill the current values, instead of during retrieval of the presentation value, though this can be used to expand f.e. string variables only once. Formulas often contain spaces or commas, if so they have to be quoted. See examples.
+
+ .. note:: NB: The Presentation set using this command doesn't alter the values retrieved by using the ``[#]`` variable reference in rules!
+
+ **Examples:**
+
+ ``TaskValueSetPresentation,bme,Temperature,'%value% {D}C'``
+
+ ``TaskValueSetPresentation,bme,Humidity,'%value%% RH'``
+
+ ``TaskValueSetPresentation,bme,Pressure,'%value% mBar'``
+
+ ``TaskValueSetPresentation,bme,DewPoint,'%value% {D}C'``
+
+ ``TaskValueSetPresentation,bme,Fahrenheit,'%value% {D}F'``
+
+ Here we define a presentation formula for all values, including the Derived values, of the BME task.
+
+ If the **Show derived values** checkbox is enabled, it could look like this for a BME280 task:
+
+ .. image:: TaskValueSetPresentation_BME280.png
+
+ **Change presentation from numeric to text:**
+
+ For another device, there is a numeric presentation of a climate system that should be presented in readable text:
+
+ In ``On System#Boot Do`` this line is added, setting a string with the mapping from numeric to text:
+
+ ``LetStr,FHC,'Undefined Fan 💨Heat 🔥Cool 🧊' // 14 characters per item``
+
+ And also:
+
+ ``TaskValueSetPresentation,dummy,Run_cycle,'\{lookup:%value%:14:`\[str#FHC\]`\} (%value%)'``
+
+ This uses the ``LookUp`` string function to convert the numeric value to a string, including an unicode smiley, representing the corresponding state. The presentation formula also shows the numeric value for the more technical inclined user.
+
+ .. image:: TaskValueSetPresentation_LookUp.png
+
+ Derived values for Cool, Fan and Heat have also been added as illustration.
+
+ This example also shows that the Derived values (all below Run_cycle) are presented in alphabetic order, not case-sensitive.
+
+ **Present extra values for Sysinfo plugin**:
+
+ In ``On System#Boot Do`` we add:
+
+ ``TaskValueSetDerived,sysinfo,InternalTemp,\[sysinfo#internaltemp\]``
+
+ ``TaskValueSetPresentation,sysinfo,InternalTemp,'%value% {D}C \%c_c2f\%(%value%) {D}F'``
+
+ This adds the Derived value for InternalTemp, that maps to the existing Get Config value ``[SysInfo#InternalTemp]``, but as that value has higher priority than the Derived value, it's not overwritten by the formula for the Derived value, but it still causes this value to be presented on the Devices page (adding a ``TaskValueSetPresentation`` doesn't, as that must also be applied to existing Task values).
+
+ The formula for ``TaskDeviceSetPresentation`` shows both the Celcius and Fahrenheit temperatures, applying the available conversion for that.
+
+ .. image:: TaskValueSetPresentation_Sysinfo.png
+
+ A simplified version of ``TaskValueSetPresentation`` is available, directly in the Formula field of a plugin (if that's available): Place a ``$`` as the first character in the Formula field, and the rest of the field will be used as a Presentation formula. The available space there is quite limited, that's why this ``TaskValueSetPresentation`` command was added. The direct value in the Formula field *overrides* the formula set via this command, though.
+ "
+ "
TimerPause","
:green:`Rules`","
Pause a timer
diff --git a/docs/source/Plugin/P003_LJ12A3.rst b/docs/source/Plugin/P003_LJ12A3.rst
index 1c8ff8afb..f7706f800 100644
--- a/docs/source/Plugin/P003_LJ12A3.rst
+++ b/docs/source/Plugin/P003_LJ12A3.rst
@@ -100,10 +100,8 @@ Sensor
.. warning:: ESP8266 GPIO 16 (D0) is not compatible with pulse counters.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
diff --git a/docs/source/Plugin/P003_TCR5000.rst b/docs/source/Plugin/P003_TCR5000.rst
index c98889261..3f297ba1c 100644
--- a/docs/source/Plugin/P003_TCR5000.rst
+++ b/docs/source/Plugin/P003_TCR5000.rst
@@ -72,10 +72,8 @@ Sensor
.. warning:: ESP8266 GPIO 16 (D0) is not compatible with pulse counters.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
diff --git a/docs/source/Plugin/P003_YFS401.rst b/docs/source/Plugin/P003_YFS401.rst
index df0d0ab1f..eca76be8a 100644
--- a/docs/source/Plugin/P003_YFS401.rst
+++ b/docs/source/Plugin/P003_YFS401.rst
@@ -79,10 +79,8 @@ Sensor
.. warning:: ESP8266 GPIO 16 (D0) is not compatible with pulse counters.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
diff --git a/docs/source/Plugin/P004_DS18b20.rst b/docs/source/Plugin/P004_DS18b20.rst
index 9253470fe..26c51fffa 100644
--- a/docs/source/Plugin/P004_DS18b20.rst
+++ b/docs/source/Plugin/P004_DS18b20.rst
@@ -220,10 +220,8 @@ The Statistics can help determining why a setup of sensors may not perform as we
-Data Acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
Values
diff --git a/docs/source/Plugin/P005_DHT11_DHT22.rst b/docs/source/Plugin/P005_DHT11_DHT22.rst
index a4bd6363a..a0f40d879 100644
--- a/docs/source/Plugin/P005_DHT11_DHT22.rst
+++ b/docs/source/Plugin/P005_DHT11_DHT22.rst
@@ -69,10 +69,8 @@ Sensor
* **Sensor model**: Pick your **sensor model**, either DHT11 or DHT22.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
diff --git a/docs/source/Plugin/P006_BMP085.rst b/docs/source/Plugin/P006_BMP085.rst
index 7a153feba..fb54b8353 100644
--- a/docs/source/Plugin/P006_BMP085.rst
+++ b/docs/source/Plugin/P006_BMP085.rst
@@ -54,10 +54,8 @@ Sensor
* **Altitude**: Set the level above sea for your unit. This is done in order to get a correct pressure value.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
Indicators (recommended settings)
diff --git a/docs/source/Plugin/P006_BMP180.rst b/docs/source/Plugin/P006_BMP180.rst
index 158696ae3..5220a01d7 100644
--- a/docs/source/Plugin/P006_BMP180.rst
+++ b/docs/source/Plugin/P006_BMP180.rst
@@ -54,10 +54,8 @@ Sensor
* **Altitude**: Set the level above sea for your unit. This is done in order to get a correct pressure value.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
Indicators (recommended settings)
diff --git a/docs/source/Plugin/P007.rst b/docs/source/Plugin/P007.rst
index 48820ce26..7d6c21fd3 100644
--- a/docs/source/Plugin/P007.rst
+++ b/docs/source/Plugin/P007.rst
@@ -118,10 +118,7 @@ The chip documentation describes it like this:
2) The internal oscillator will be enabled continuesly, causing higher power usage, but also improved measurements when reading an analog input as it avoids the startup time of the oscillator that can cause inaccurate readings.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P009.rst b/docs/source/Plugin/P009.rst
index cba06c135..ba17f11e9 100644
--- a/docs/source/Plugin/P009.rst
+++ b/docs/source/Plugin/P009.rst
@@ -110,10 +110,7 @@ Advanced event management
* **Use safe button (slower)**: This effectively adds an extra De-bounce delay and sends event value ``4`` when reached.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P011.rst b/docs/source/Plugin/P011.rst
index 2d7a0fb7a..49d87eb1d 100644
--- a/docs/source/Plugin/P011.rst
+++ b/docs/source/Plugin/P011.rst
@@ -68,10 +68,7 @@ Device Settings
* *Input (switch)*: Act like an input switch, the pin is read every 20 msec, and if the state changes, the new state is reported as an event, with the new value. The **Interval** is ignored when this Port Type is selected.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P013.rst b/docs/source/Plugin/P013.rst
index 25789dc93..823e2f2d6 100644
--- a/docs/source/Plugin/P013.rst
+++ b/docs/source/Plugin/P013.rst
@@ -115,11 +115,8 @@ Sensor
* **Trigger width**: For hardware that doesn't exactly follow the timing guidelines, it may be needed to extend the pulse delay somewhat to receive a usable echo. The default is 10 microsoconds, and can be increased in small steps to max. 20 microseconds.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Single event with all values**: This setting combines all values for the plugin in a single ``#All,[,]`` event.
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (1..5 seconds is normal for the ``Value`` or ``Combined`` settings, 0 for the ``State`` setting).
Indicators (recommended settings)
diff --git a/docs/source/Plugin/P015_TSL2561.rst b/docs/source/Plugin/P015_TSL2561.rst
index 9f91f2593..7e3c127fd 100644
--- a/docs/source/Plugin/P015_TSL2561.rst
+++ b/docs/source/Plugin/P015_TSL2561.rst
@@ -74,10 +74,8 @@ Sensor
* **Send sensor to sleep**: Have it checked in order to save the unit from wear, especially good if you do measurements with long intervals in between.
* **Enable 16x Gain**: Have it checked in order to get higher readings in low light areas.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
Indicators (recommended settings)
diff --git a/docs/source/Plugin/P017.rst b/docs/source/Plugin/P017.rst
index 4911bcb3b..882afbc72 100644
--- a/docs/source/Plugin/P017.rst
+++ b/docs/source/Plugin/P017.rst
@@ -51,6 +51,8 @@ Depending on the type of ESPEasy build installed on the unit, the I2C settings m
**Event on Tag removal** (Disabled by default) When enabled sends the removed Tag value as an event and to all enabled controllers.
+.. include:: DataAcquisition.repl
+
Supported hardware
------------------
diff --git a/docs/source/Plugin/P019.rst b/docs/source/Plugin/P019.rst
index f9ff245d7..678b229d6 100644
--- a/docs/source/Plugin/P019.rst
+++ b/docs/source/Plugin/P019.rst
@@ -112,10 +112,7 @@ Advanced event management
* **Use safe button (slower)**: This effectively adds an extra De-bounce delay and sends event value ``4`` when reached.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P020.rst b/docs/source/Plugin/P020.rst
index 29ebcc277..5d9732003 100644
--- a/docs/source/Plugin/P020.rst
+++ b/docs/source/Plugin/P020.rst
@@ -136,10 +136,7 @@ Led
* **Led inverted**: Iverts the on/off state for the Led.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition and Send to Controller settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. *Single event with all values* option is not applicable for this plugin.
+.. include:: DataAcquisition.repl
Commands
diff --git a/docs/source/Plugin/P021.rst b/docs/source/Plugin/P021.rst
index d0ae31017..3578f33af 100644
--- a/docs/source/Plugin/P021.rst
+++ b/docs/source/Plugin/P021.rst
@@ -160,10 +160,7 @@ Sensor
:sup:`1)` Configuration may not be available due to build size limitations.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P026.rst b/docs/source/Plugin/P026.rst
index ec0e8b011..0d3e873eb 100644
--- a/docs/source/Plugin/P026.rst
+++ b/docs/source/Plugin/P026.rst
@@ -74,10 +74,7 @@ Output configuration
.. .. include:: P026_events.repl
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected, and event(s) generated, using this interval.
diff --git a/docs/source/Plugin/P027.rst b/docs/source/Plugin/P027.rst
index 742c948cd..73670b7b1 100644
--- a/docs/source/Plugin/P027.rst
+++ b/docs/source/Plugin/P027.rst
@@ -81,10 +81,7 @@ Device Settings
* **Use Powerdown mode**: When enabled will bring the sensor in Powerdown mode after initialization, wake it from that mode to start measuring when requesting a read (either via TaskRun or by Interval), and after reading the values bring it back in Powerdown mode. This mode is available to reduce the quiescent current used during sleep mode in battery operated setups.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P028.rst b/docs/source/Plugin/P028.rst
index 80c4c979d..529143a4c 100644
--- a/docs/source/Plugin/P028.rst
+++ b/docs/source/Plugin/P028.rst
@@ -78,8 +78,7 @@ Device Settings
* **Temperature offset** Depending on the sensor and the location of the sensor, it may be required to apply some temperature compensation. This can be set in steps of 0.1 degree. When using a BME280 sensor, this also applies a compensation to the **Humidity** reading.
-Data Acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
* **Temperature Error Value** If no sensor is connected, or the sensor suffers from read errors for some reason, the **Temperature** value to report in that situation can be set here. Both **Humidity** and **Pressure** will be set to -1 when such read errors occur.
diff --git a/docs/source/Plugin/P033.rst b/docs/source/Plugin/P033.rst
index fb978ecff..96d782ac9 100644
--- a/docs/source/Plugin/P033.rst
+++ b/docs/source/Plugin/P033.rst
@@ -116,8 +116,7 @@ For example:
-Data Acquisition
-----------------
+.. include:: DataAcquisition.repl
In ESPEasy, a task can be triggered to yield some new sample data.
For other plugins, this means the sensor is read and when successful there is some new data to process.
diff --git a/docs/source/Plugin/P034.rst b/docs/source/Plugin/P034.rst
index fec0aaee7..7a01dab26 100644
--- a/docs/source/Plugin/P034.rst
+++ b/docs/source/Plugin/P034.rst
@@ -53,10 +53,7 @@ Device Settings
This device has no further configuration settings.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P043.rst b/docs/source/Plugin/P043.rst
index ee619c923..ffcc91639 100644
--- a/docs/source/Plugin/P043.rst
+++ b/docs/source/Plugin/P043.rst
@@ -66,10 +66,7 @@ Output Configuration
* **Number Output Values**: Select Single (default), Dual, Triple or Quad. The Triple and Quad options aren't actually used in this plugin.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P045.rst b/docs/source/Plugin/P045.rst
index b2527838f..7c0293799 100644
--- a/docs/source/Plugin/P045.rst
+++ b/docs/source/Plugin/P045.rst
@@ -84,10 +84,7 @@ When **Function** is set to one of the measurement options, **Range acceleration
.. image:: P045_All3Values.png
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval**: By default, Interval will be set to 60 sec. The minimum value allowed is 1 sec.
diff --git a/docs/source/Plugin/P046.rst b/docs/source/Plugin/P046.rst
index 063e6c6bf..2e8f055dc 100644
--- a/docs/source/Plugin/P046.rst
+++ b/docs/source/Plugin/P046.rst
@@ -148,10 +148,8 @@ Sensor
.. warning:: You must not have more than 1 main plugin function active! It will not work
properly if you have more.
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (5..15 seconds is normal).
Indicators (recommended settings)
diff --git a/docs/source/Plugin/P047.rst b/docs/source/Plugin/P047.rst
index 1bf6b3386..6567d0536 100644
--- a/docs/source/Plugin/P047.rst
+++ b/docs/source/Plugin/P047.rst
@@ -106,10 +106,7 @@ When using the I2C scanner, the changed address will show up, but it probably wi
.. note:: The newly set address is permanently stored in the sensor, and can be changed again at a later time if desired.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 60 sec. The minimum value allowed is 1 sec. but each read is taking at least 2 seconds, so selecting a value below 3 seonds might cause intermittent read failures or invalid values.
diff --git a/docs/source/Plugin/P052.rst b/docs/source/Plugin/P052.rst
index db6d3db7a..0890a8a64 100644
--- a/docs/source/Plugin/P052.rst
+++ b/docs/source/Plugin/P052.rst
@@ -105,6 +105,8 @@ Sensor
See: :ref:`SerialHelper_page`
+.. include:: DataAcquisition.repl
+
Commands available
^^^^^^^^^^^^^^^^^^
diff --git a/docs/source/Plugin/P053.rst b/docs/source/Plugin/P053.rst
index 94fbe6c5b..e44ed465e 100644
--- a/docs/source/Plugin/P053.rst
+++ b/docs/source/Plugin/P053.rst
@@ -327,6 +327,8 @@ References:
.. .. |P053_usedby|
+.. include:: DataAcquisition.repl
+
Commands available
^^^^^^^^^^^^^^^^^^
diff --git a/docs/source/Plugin/P059.rst b/docs/source/Plugin/P059.rst
index 4b004447f..c25af1f86 100644
--- a/docs/source/Plugin/P059.rst
+++ b/docs/source/Plugin/P059.rst
@@ -92,10 +92,7 @@ Sensor
* **Limit max**: The highest value that the counter will go to. As with the min limit if the counter is above this limit it will descend to the max level but never climb over it once reached below it.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 0 sec for this plugin, as this setting is optional. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P061.rst b/docs/source/Plugin/P061.rst
index daf2c2036..63134f09b 100644
--- a/docs/source/Plugin/P061.rst
+++ b/docs/source/Plugin/P061.rst
@@ -137,10 +137,7 @@ Available options:
.. image:: P061_ChipModeOptions.png
:alt: Chip mode options
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec.
diff --git a/docs/source/Plugin/P062.rst b/docs/source/Plugin/P062.rst
index 22362cb1d..511614729 100644
--- a/docs/source/Plugin/P062.rst
+++ b/docs/source/Plugin/P062.rst
@@ -116,10 +116,7 @@ To have better control per touch key, for each key a separate Touch and Release
* **Clear calibrationdata** This checkbox can be checked to clear the calibration data from the sensor, so it can restart the calibration process. This will be applied when the Submit button is used *or* the page is reloaded by changing the **Enable Calibration** option!. The initial setting is always unchecked, and is not stored.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec (off). If a value > 0 is used, then every seconds an event with the last value is generated.
diff --git a/docs/source/Plugin/P064.rst b/docs/source/Plugin/P064.rst
index e20e31d38..901a6ed27 100644
--- a/docs/source/Plugin/P064.rst
+++ b/docs/source/Plugin/P064.rst
@@ -88,6 +88,8 @@ Event generation
* **Separate Gesture events**: When enabled will generate a ``#Swipe=`` event (see below), independent from **Interval**. This allows gestures to be handled independently from measuring the proximity and ambient light, or R/G/B colors. The ``Gesture`` value will be updated for every gesture detected, but the Values events will only trigger on Interval, if this checkbox is enabled.
+.. include:: DataAcquisition.repl
+
Values
^^^^^^
diff --git a/docs/source/Plugin/P067.rst b/docs/source/Plugin/P067.rst
index cca06eaef..8bab4f204 100644
--- a/docs/source/Plugin/P067.rst
+++ b/docs/source/Plugin/P067.rst
@@ -148,10 +148,7 @@ To calibrate your measurements to a defined range, f.e. ``kg`` or ``g``, this pr
.. note::
Disclaimer: As the load versus output signal for a load cell isn't guaranteed to be linear, calibration using the minimum and maximum expected load for calibration will result in a best effort output.
-Data Acquisition
-----------------
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The minimum value allowed is 1 sec.
diff --git a/docs/source/Plugin/P077.rst b/docs/source/Plugin/P077.rst
index 09f9321dd..f60d887e2 100644
--- a/docs/source/Plugin/P077.rst
+++ b/docs/source/Plugin/P077.rst
@@ -64,10 +64,7 @@ Device Settings
* **P Ref**: The calibration value for the Power measurement. When set to 0, a default value of ``12530`` will be used, see Calibration, below.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-Single event with all values, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. as this is optional for this plugin.
diff --git a/docs/source/Plugin/P080.rst b/docs/source/Plugin/P080.rst
index 279b294a5..2ec21ee4f 100644
--- a/docs/source/Plugin/P080.rst
+++ b/docs/source/Plugin/P080.rst
@@ -83,10 +83,7 @@ Device Settings
* **Event with iButton address**: With this option enabled, and the **Device Address** selection left to ``- None -``, instead of responding to a single iButton, an event is generated for any iButton that is recognized by the receiver. And once removed, the same event is generated without the iButton address. This can be processed in rules. See below in the **Events** chapter for a more detailed description.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. as it is optional. It is the frequency used to send the value to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P082.rst b/docs/source/Plugin/P082.rst
index 63aae4866..5b3c3dea5 100644
--- a/docs/source/Plugin/P082.rst
+++ b/docs/source/Plugin/P082.rst
@@ -238,8 +238,7 @@ Some packets of a controller like the LoRaWAN (TTN) controller, may get lost dur
This way the received samples can still be grouped together and if the travelled path is predictable these samples can even be tracked back to the most likely position where they may have been taken.
-Data Acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
The GPS receiver yields more measurement values than the max. 4 that can be used in a task.
When the GPS receiver is linked to the LoRaWAN (TTN) controller, all relevant received data will be transmitted.
diff --git a/docs/source/Plugin/P087.rst b/docs/source/Plugin/P087.rst
index b6cba475f..fe50fe1d2 100644
--- a/docs/source/Plugin/P087.rst
+++ b/docs/source/Plugin/P087.rst
@@ -90,10 +90,7 @@ Statistic data is only visible if the plugin is configured and enabled.
This shows the latest data received and some statistics.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec.
diff --git a/docs/source/Plugin/P089.rst b/docs/source/Plugin/P089.rst
index cd9d47bc7..ea690971f 100644
--- a/docs/source/Plugin/P089.rst
+++ b/docs/source/Plugin/P089.rst
@@ -40,10 +40,7 @@ Device Settings
* **Hostname**: The hostname or IP-address for the device or host to monitor.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. Every Interval setting, the host will be pinged and the state will be updated in Fails. If the ping responds as intended, then Fails is reset to 0.
diff --git a/docs/source/Plugin/P092_ESR21.rst b/docs/source/Plugin/P092_ESR21.rst
index 97f3c0852..77098764a 100644
--- a/docs/source/Plugin/P092_ESR21.rst
+++ b/docs/source/Plugin/P092_ESR21.rst
@@ -69,10 +69,8 @@ Inputs
"Current power", "25,26", "Heat power [kW]", "", "1"
"Heat quantity", "27 ... 30", "Heat meter [MWh]", "", "4"
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (10..60 seconds is normal).
.. note:: Interval not less than **10 seconds**! The reading of the DL bus happens twice per interval.
diff --git a/docs/source/Plugin/P092_UVR1611.rst b/docs/source/Plugin/P092_UVR1611.rst
index d1ab4445c..fb4fe49f6 100644
--- a/docs/source/Plugin/P092_UVR1611.rst
+++ b/docs/source/Plugin/P092_UVR1611.rst
@@ -73,10 +73,8 @@ Inputs
"Heat quantity 1", "52 ... 55", "Heat meter [MWh]", "1", "4"
"Heat quantity 2", "60 ... 64", "Heat meter [MWh]", "2", "4"
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (10..60 seconds is normal).
.. note:: Interval not less than **10 seconds**! The reading of the DL bus happens twice per interval.
diff --git a/docs/source/Plugin/P092_UVR31.rst b/docs/source/Plugin/P092_UVR31.rst
index 4f0e28242..851bb897e 100644
--- a/docs/source/Plugin/P092_UVR31.rst
+++ b/docs/source/Plugin/P092_UVR31.rst
@@ -63,10 +63,8 @@ Inputs
"Temp1 ... Temp3", "2 ... 7", "Sensor", "1 - 3", "1"
"Output byte", "8", "Digital Output", "", "0"
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (20..60 seconds is normal).
.. note:: Interval not less than **20 seconds**! The reading of the DL bus happens twice per interval.
diff --git a/docs/source/Plugin/P092_UVR42.rst b/docs/source/Plugin/P092_UVR42.rst
index eb8ed4f59..b2f381628 100644
--- a/docs/source/Plugin/P092_UVR42.rst
+++ b/docs/source/Plugin/P092_UVR42.rst
@@ -63,10 +63,8 @@ Inputs
"Temp1 ... Temp4", "2 ... 9", "Sensor", "1 - 4", "1"
"Output byte", "10", "Digital Output", "1 - 2", "0"
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (20..60 seconds is normal).
.. note:: Interval not less than **20 seconds**! The reading of the DL bus happens twice per interval.
diff --git a/docs/source/Plugin/P092_UVR61-3.rst b/docs/source/Plugin/P092_UVR61-3.rst
index f97a30a14..a11cfb8e4 100644
--- a/docs/source/Plugin/P092_UVR61-3.rst
+++ b/docs/source/Plugin/P092_UVR61-3.rst
@@ -71,10 +71,8 @@ Inputs
"Current power", "27,28", "Heat power [kW]", "", "1"
"Heat quantity", "29 ... 34", "Heat meter [MWh]", "", "4"
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (10..60 seconds is normal).
.. note:: Interval not less than **10 seconds**! The reading of the DL bus happens twice per interval.
diff --git a/docs/source/Plugin/P092_UVR61-3v8_3.rst b/docs/source/Plugin/P092_UVR61-3v8_3.rst
index 5d92d8995..6fbd34a8a 100644
--- a/docs/source/Plugin/P092_UVR61-3v8_3.rst
+++ b/docs/source/Plugin/P092_UVR61-3v8_3.rst
@@ -76,10 +76,8 @@ Inputs
"Current power 3", "56, 57", "Heat power [kW]", "3", "1"
"Heat quantity 3", "58 ... 61", "Heat meter [MWh]", "3", "4"
-Data acquisition
-^^^^^^^^^^^^^^^^
+.. include:: DataAcquisition.repl
-* **Send to controller** 1..3: Check which controller (if any) you want to publish to. All or no controller can be used.
* **Interval**: How often should the task publish its value (10..60 seconds is normal).
.. note:: Interval not less than **10 seconds**! The reading of the DL bus happens twice per interval.
diff --git a/docs/source/Plugin/P098.rst b/docs/source/Plugin/P098.rst
index 85ca76ee1..48f78edcf 100644
--- a/docs/source/Plugin/P098.rst
+++ b/docs/source/Plugin/P098.rst
@@ -98,10 +98,7 @@ Limit Switches
* **Limit B Debounce**: The limit-switch debounce time, range 0 to 1000 msec.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval**: Interval is optional, and will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P100.rst b/docs/source/Plugin/P100.rst
index dba1dd2ed..c2315e01c 100644
--- a/docs/source/Plugin/P100.rst
+++ b/docs/source/Plugin/P100.rst
@@ -54,10 +54,7 @@ Device Settings
.. image:: P100_CountTotalEnabled.png
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced).
diff --git a/docs/source/Plugin/P102.rst b/docs/source/Plugin/P102.rst
index 35cbac8cf..12030e886 100644
--- a/docs/source/Plugin/P102.rst
+++ b/docs/source/Plugin/P102.rst
@@ -104,6 +104,7 @@ Sensor
See: :ref:`SerialHelper_page`
+.. include:: DataAcquisition.repl
.. Events
.. ~~~~~~
diff --git a/docs/source/Plugin/P103.rst b/docs/source/Plugin/P103.rst
index e2c984bca..3919afe46 100644
--- a/docs/source/Plugin/P103.rst
+++ b/docs/source/Plugin/P103.rst
@@ -141,10 +141,7 @@ To make the Temperature and Dew-point available for use, extra values are availa
.. image:: P103_Device_HUM_Values.png
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P105.rst b/docs/source/Plugin/P105.rst
index b1c030e61..ead7d850c 100644
--- a/docs/source/Plugin/P105.rst
+++ b/docs/source/Plugin/P105.rst
@@ -82,10 +82,7 @@ When selecting the **AHT1x** Sensor model, an extra option is made available:
* **Temperature offset** Depending on the sensor and the location of the sensor, it may be required to apply some temperature compensation. This can be set in steps of 0.1 degree. This also applies a compensation to the **Humidity** reading.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P106.rst b/docs/source/Plugin/P106.rst
index ebaf32c01..1bbf059a3 100644
--- a/docs/source/Plugin/P106.rst
+++ b/docs/source/Plugin/P106.rst
@@ -58,10 +58,7 @@ Device Settings
* **Present `Gas` in Ohm (not kOhm)**: Normally the Gas resistance value, received from the MOX Gas sensor, is divided to be presented as kOhm, but with this setting checked, the native Ohm value is shown.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P109.rst b/docs/source/Plugin/P109.rst
index 0c07cb87b..061ebefff 100644
--- a/docs/source/Plugin/P109.rst
+++ b/docs/source/Plugin/P109.rst
@@ -96,10 +96,7 @@ Device Settings
* **Delay on setpoint change**: Configure the delay in seconds before the relay state will be changed after the setpoint is changed. Range: 1..10 seconds. This is to avoid flipping the heating on/off rapidly when changing the setpoint value.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P110.rst b/docs/source/Plugin/P110.rst
index 48ec9d372..81b334023 100644
--- a/docs/source/Plugin/P110.rst
+++ b/docs/source/Plugin/P110.rst
@@ -68,8 +68,8 @@ Device Settings
* **Trigger delta** To avoid triggering many events with only a small difference in distance the 'Trigger delta' option is available. This can be set to only trigger an event when the new distance is at least the delta less or more than the previous measurement.
NB: This setting is ignored if 'Send event when value unchanged' is checked!
-n
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+
+.. include:: DataAcquisition.repl
``Interval`` can now be set to 0, causing events to only be generated when changed, with ``Trigger delta`` calculated in.
diff --git a/docs/source/Plugin/P111.rst b/docs/source/Plugin/P111.rst
index aea9f6d14..dffe177d1 100644
--- a/docs/source/Plugin/P111.rst
+++ b/docs/source/Plugin/P111.rst
@@ -60,6 +60,8 @@ MFRC522 based NFC RFID reader connected via SPI.
|P111_usedby|
+.. include:: DataAcquisition.repl
+
.. Commands available
.. ^^^^^^^^^^^^^^^^^^
diff --git a/docs/source/Plugin/P112.rst b/docs/source/Plugin/P112.rst
index 8af073251..a18c09944 100644
--- a/docs/source/Plugin/P112.rst
+++ b/docs/source/Plugin/P112.rst
@@ -26,6 +26,8 @@ Supported hardware
|P112_usedby|
+.. include:: DataAcquisition.repl
+
.. Commands available
.. ^^^^^^^^^^^^^^^^^^
diff --git a/docs/source/Plugin/P113.rst b/docs/source/Plugin/P113.rst
index 3bfbda6ef..b16b3810f 100644
--- a/docs/source/Plugin/P113.rst
+++ b/docs/source/Plugin/P113.rst
@@ -104,10 +104,7 @@ If a value is selected that's invalid, like when the optical center is too close
.. note:: To not overly complicate/expand the used javascript code, ROI selection works from left-top to right-bottom, and not in other directions.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. Setting this to 1 or 2 seconds, usually offers a reasonable response time.
diff --git a/docs/source/Plugin/P114.rst b/docs/source/Plugin/P114.rst
index d474ddbed..8d414ebc6 100644
--- a/docs/source/Plugin/P114.rst
+++ b/docs/source/Plugin/P114.rst
@@ -75,7 +75,7 @@ Usually a higher integration time results in a more accurate measurement.
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 60 sec. The minimum value allowed is 1 sec. but for measuring UV values, that don't change very quickly, reasonable values should be between 10 and 300 sec.
diff --git a/docs/source/Plugin/P115.rst b/docs/source/Plugin/P115.rst
index 82d30f62f..5d72a2f89 100644
--- a/docs/source/Plugin/P115.rst
+++ b/docs/source/Plugin/P115.rst
@@ -57,6 +57,8 @@ Device Settings
**Send Event on Alert** : Send an event, to be used in rules, when the alert is set. This can be cleared with ``max1704xclearalert``
+.. include:: DataAcquisition.repl
+
Values
^^^^^^
diff --git a/docs/source/Plugin/P116.rst b/docs/source/Plugin/P116.rst
index 103d877a6..4e7621293 100644
--- a/docs/source/Plugin/P116.rst
+++ b/docs/source/Plugin/P116.rst
@@ -28,7 +28,7 @@ The ST7735, ST7789 and ST7796 chip families drive color TFT displays in various
This plugin supports these display models:
-* **ST7735** with resolutions 128 x 128, 128 x 160, 80 x 160, 135 x 240 and 172 x 320 pixels
+* **ST7735** with resolutions 128 x 128, 128 x 160, 80 x 160, 135 x 240, 170 x 320 and 172 x 320 pixels
* **ST7789** with resolutions 240 x 320, 240 x 240, 240 x 280 and 135 x 240 pixels
* **ST7796** with resolution of 320 x 480 pixels.
@@ -141,10 +141,11 @@ Available options:
* *ST7735 128 x 128px*: Allows about 12 lines of text in the smallest font scaling setting (the 13th line will be distorted, as the bottom 2 pixellines aren't available)
* *ST7735 128 x 160px*: Allows 16 lines of text in the smallest font scaling setting.
-* *ST7735 80 x 160px*{ Allows 16 lines of text in the smallest font scaling setting.
+* *ST7735 80 x 160px*: Allows 16 lines of text in the smallest font scaling setting.
* *ST7735 80 x 160px (Color inverted)*: Special color inverted configuration as used in f.e. M5Stack StickC.
* *ST7735 135 x 240px*: Added to support a revision of the TTGO T-Display 16MB Flash module, that won't work with the ST7789 driver, the seller is claiming to use, but does work with this specially crafted ST7735 driver.
* *ST7735 172 x 320px*: Added to support an ESP32-C6 module having a display with this specific resolution.
+* *ST77xx 170 x 320px*: Added to support ST7789v3 / ST7735 displays with this specific resolution.
* *ST7789 240 x 320px*: Allows 32 lines of text in the smallest font scaling setting. Predefined text only goes to 24, extra lines can be displayed from rules or external commands.
* *ST7789 240 x 240px*: Allows 24 lines of text in the smallest font scaling setting.
* *ST7789 240 x 280px*: Allows 28 lines of text in the smallest font scaling setting. Predefined text only goes to 24, extra lines can be displayed from rules or external commands.
diff --git a/docs/source/Plugin/P117.rst b/docs/source/Plugin/P117.rst
index eba25ac10..9fe2008e6 100644
--- a/docs/source/Plugin/P117.rst
+++ b/docs/source/Plugin/P117.rst
@@ -59,7 +59,7 @@ Device Settings
.. warning:: Automatic Self Calibration is a tedious process, that at first run takes at least 7 days to complete, and requires the sensor to be in fresh air for at least 1 hour daily, during the self-calibration period.
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be measured and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P118.rst b/docs/source/Plugin/P118.rst
index 99f904ec8..dc5a12ade 100644
--- a/docs/source/Plugin/P118.rst
+++ b/docs/source/Plugin/P118.rst
@@ -80,10 +80,7 @@ Remote RF Controls
* **Enable Orcon support**: Enables the extra protocol for the Orcon brand of ventilation units, that use the same protocol, but with a different command set. To avoid possible interference, it is disabled by default.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P119.rst b/docs/source/Plugin/P119.rst
index a7c829928..ba315baea 100644
--- a/docs/source/Plugin/P119.rst
+++ b/docs/source/Plugin/P119.rst
@@ -60,10 +60,7 @@ The available options:
* *10x per second* The default setting, can be used if the movement of the module is slow or medium fast.
* *50x per second* Can be used if the movement of the module is fast, or a high update interval is needed, then the smoothing of the results can be enhanced by increasing the **Averaging buffer size**.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 0 sec. It is the frequency used to read the sensor values, averaged by the Averaging buffer, and send these to any Controller(s) configured for this device.
diff --git a/docs/source/Plugin/P120_P125_common.repl b/docs/source/Plugin/P120_P125_common.repl
index ccad09fe4..cd5c904b1 100644
--- a/docs/source/Plugin/P120_P125_common.repl
+++ b/docs/source/Plugin/P120_P125_common.repl
@@ -135,10 +135,7 @@ Data retrieval
* **Measuring frequency**: The plugin supports 2 measuring frequencies, 10x per second or 50x per second. When using 50x per second, it will stabilize the measurements if the **Averaging buffer size** is also increased, f.e. to 50 or 100. This may increase the load on the ESP unit somewhat.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P121.rst b/docs/source/Plugin/P121.rst
index 5e2f48657..e974ac663 100644
--- a/docs/source/Plugin/P121.rst
+++ b/docs/source/Plugin/P121.rst
@@ -55,6 +55,8 @@ Device Settings
* **DeclinationAngle**: In degree. For getting the heading a correction value need to be entered. Determine the value at https://www.magnetic-declination.com/. If set to 0.00 the direction is slightly off.
+.. include:: DataAcquisition.repl
+
Values
^^^^^^
diff --git a/docs/source/Plugin/P122.rst b/docs/source/Plugin/P122.rst
index 7d312650c..4fcc96aa8 100644
--- a/docs/source/Plugin/P122.rst
+++ b/docs/source/Plugin/P122.rst
@@ -59,6 +59,7 @@ Configuration
**Resolution**: Select one of the supported resolution settings of the device. See datasheet.
+.. include:: DataAcquisition.repl
Change log
----------
diff --git a/docs/source/Plugin/P124.rst b/docs/source/Plugin/P124.rst
index 3d9ecd1e7..b593324a4 100644
--- a/docs/source/Plugin/P124.rst
+++ b/docs/source/Plugin/P124.rst
@@ -112,10 +112,7 @@ When changing the number of relays, the page will be submitted and the settings
* **Loop Channel/Get on read**: When enabled, will update the Channel and Get values with the selected channel and its current state (0=off, 1=on). The channel will be incremented each interval (read action).
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P126.rst b/docs/source/Plugin/P126.rst
index 9c56f8f55..5e79b017a 100644
--- a/docs/source/Plugin/P126.rst
+++ b/docs/source/Plugin/P126.rst
@@ -92,9 +92,7 @@ If either of the Hex/bin options is selected, but no ``Values display (Off=Hex/O
* **Restore Values on warm boot**: By default, the register states are restored from RTC memory, so the outputs are in their previous state after a warm boot of the ESP. When turned off, all outputs will be turned off (low) on the next update.
-
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval**: By default, Interval will be set to 0 sec. as this is optional for this plugin. Every Interval setting, the current output states will be read from the runtime object (*not* from the shift registers!) and stored in the Values fields, so they can be restored on a warm boot of the ESP.
diff --git a/docs/source/Plugin/P127.rst b/docs/source/Plugin/P127.rst
index f23420650..1c0293009 100644
--- a/docs/source/Plugin/P127.rst
+++ b/docs/source/Plugin/P127.rst
@@ -59,10 +59,7 @@ Device Settings
* **Altitude**: To compensate for local air pressure, the altitude relative to sea-level of the location where the sensor is installed can be entered. As usual for this type of setting, the altitude is registered in ``meter``.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The minimum value allowed is 1 sec. but for measuring CO2 values, that don't change very quickly, reasonable values could be between 10 and 300 sec.
diff --git a/docs/source/Plugin/P128.rst b/docs/source/Plugin/P128.rst
index 455f5b8b3..4c4cbf517 100644
--- a/docs/source/Plugin/P128.rst
+++ b/docs/source/Plugin/P128.rst
@@ -62,10 +62,7 @@ Due to the design of the used library and the methods used, on ESP8266 only GPIO
* **Max brightness**: The maximum brightness allowed for the stripe. Range: 1..255. This is also the initial brightness set during initialization. Can *not* be overridden by the ``dim`` subcommand, and also the maximum value for the ``rainbow`` subcommand.
-Data Aquisition
-~~~~~~~~~~~~~~~
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 0 sec. This is optional. When set, the **Values** will be sent to any configured controllers, and events generated to be handled in rules.
diff --git a/docs/source/Plugin/P129.rst b/docs/source/Plugin/P129.rst
index 8cd6aca94..662972271 100644
--- a/docs/source/Plugin/P129.rst
+++ b/docs/source/Plugin/P129.rst
@@ -106,7 +106,7 @@ When all 16 chips are configured, the configuration looks like this:
.. image:: P129_EventConfiguration.png
:alt: Event configuration
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. as this is optional. Every Interval setting, the current output states will be read from the runtime object (*not* from the shift registers!) and stored in the Values fields, so they can be restored on a warm boot of the ESP.
diff --git a/docs/source/Plugin/P132.rst b/docs/source/Plugin/P132.rst
index 655990d52..9e36b88a5 100644
--- a/docs/source/Plugin/P132.rst
+++ b/docs/source/Plugin/P132.rst
@@ -96,10 +96,7 @@ To calculate the minimally required **Interval** setting for the task, this form
| Example: (Default case) 3 * 1 * 0.00144 + 3 * 1 * 0.00144 = 0.008 seconds => 1 minimum Interval.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P133.rst b/docs/source/Plugin/P133.rst
index cd63cbd01..9abb02060 100644
--- a/docs/source/Plugin/P133.rst
+++ b/docs/source/Plugin/P133.rst
@@ -79,10 +79,7 @@ The time that is needed to determine the measurement increases with the resoluti
The Gain and Resolution are factored in for the calculation of the UVIndex and Lux values.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P134.rst b/docs/source/Plugin/P134.rst
index cc417026f..d56e2083b 100644
--- a/docs/source/Plugin/P134.rst
+++ b/docs/source/Plugin/P134.rst
@@ -41,10 +41,7 @@ Sensor
See: :ref:`SerialHelper_page`
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P135.rst b/docs/source/Plugin/P135.rst
index be6579ac4..d88317a5e 100644
--- a/docs/source/Plugin/P135.rst
+++ b/docs/source/Plugin/P135.rst
@@ -74,10 +74,7 @@ When this setting is changed, the page is saved and reloaded to show/hide the ex
.. warning:: Automatic Self Calibration is a useful process to ensure long term stability of the sensor, and assumes the sensor is exposed to fresh air of 400 ppm at least once per 7 days.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P137.rst b/docs/source/Plugin/P137.rst
index 87f9ae9db..8050593fb 100644
--- a/docs/source/Plugin/P137.rst
+++ b/docs/source/Plugin/P137.rst
@@ -136,10 +136,7 @@ Available options:
.. note:: Not all options hold usable values for all boards, some may even be not connected. Check the board documentation for available values.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 0 sec. as it is optional. When set > 0 it is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P138.rst b/docs/source/Plugin/P138.rst
index 2fe25a207..86c61af15 100644
--- a/docs/source/Plugin/P138.rst
+++ b/docs/source/Plugin/P138.rst
@@ -79,10 +79,7 @@ Available options:
* *Power source*: The power source used by the ESP, 0 = battery, 1 = External (USB) power.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** By default, Interval will be set to 0 sec. as it is optional. When set > 0 it is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P139.rst b/docs/source/Plugin/P139.rst
index 2136fccd5..8ee3f22ab 100644
--- a/docs/source/Plugin/P139.rst
+++ b/docs/source/Plugin/P139.rst
@@ -154,10 +154,7 @@ Available options:
.. note:: Not all options hold usable values for all boards, some may even be not connected. Check the board documentation for available values.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. as it is optional. When set > 0 it is the frequency used to read sensor values and send these to any Controllers configured for this device.
diff --git a/docs/source/Plugin/P140.rst b/docs/source/Plugin/P140.rst
index 28dc70b04..27809d207 100644
--- a/docs/source/Plugin/P140.rst
+++ b/docs/source/Plugin/P140.rst
@@ -61,10 +61,7 @@ If the plugin is enabled, the **Current buffer content** is shown in this sectio
.. image:: P140_BufferContentExample.png
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
**Interval** Is not available for this plugin, events will only be generated when the **Send event on keypress** setting is enabled, or **Accept input only** is enabled and ``Enter`` is pressed.
diff --git a/docs/source/Plugin/P142.rst b/docs/source/Plugin/P142.rst
index 63ba48761..df437914a 100644
--- a/docs/source/Plugin/P142.rst
+++ b/docs/source/Plugin/P142.rst
@@ -140,10 +140,7 @@ Output Configuration
* *Has magnet*: The state of an available magnet (0 = no magnet, 1 = magnet detected).
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval for this plugin is set to 0 sec. as events will be generated when a measurement is available. When an Interval is set, the data will be collected and optionally sent to any configured controllers using the interval.
diff --git a/docs/source/Plugin/P143.rst b/docs/source/Plugin/P143.rst
index aefee3af7..cbb9cc872 100644
--- a/docs/source/Plugin/P143.rst
+++ b/docs/source/Plugin/P143.rst
@@ -172,10 +172,7 @@ NB: Colors will not be exact when a lower value is selected for **Initial bright
* **Led(s) off on exit**: When checked, all controlled leds on the encoders will be turned off, either by setting the color to black, or setting the encoder position to 0 (DFRobot).
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P144.rst b/docs/source/Plugin/P144.rst
index 03e1af7db..3d2cbfd37 100644
--- a/docs/source/Plugin/P144.rst
+++ b/docs/source/Plugin/P144.rst
@@ -73,10 +73,12 @@ Sensor
See :ref:`SerialHelper_page` for the available options.
+.. include:: DataAcquisition.repl
+
Commands
--------
-This plugin does not support additinal commands.
+This plugin does not support additional commands.
Change log
----------
diff --git a/docs/source/Plugin/P145.rst b/docs/source/Plugin/P145.rst
index c212b68ce..a29d5a73c 100644
--- a/docs/source/Plugin/P145.rst
+++ b/docs/source/Plugin/P145.rst
@@ -216,6 +216,8 @@ On ESP32 the analog input can be selected.
* **Analog Pin** Select one of the available analog input pins on the ESP32
+.. include:: DataAcquisition.repl
+
Commands
----------
diff --git a/docs/source/Plugin/P146.rst b/docs/source/Plugin/P146.rst
index 0b5737061..47cee7523 100644
--- a/docs/source/Plugin/P146.rst
+++ b/docs/source/Plugin/P146.rst
@@ -85,10 +85,7 @@ Available options:
Included in the Github repository is an example Python script to decode the binary format. This can be found in the ``misc/CacheController`` folder.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
Values
^^^^^^
diff --git a/docs/source/Plugin/P147.rst b/docs/source/Plugin/P147.rst
index 42882f05f..aad053e19 100644
--- a/docs/source/Plugin/P147.rst
+++ b/docs/source/Plugin/P147.rst
@@ -83,10 +83,7 @@ After selecting a Temperature task/value and Humidity task/value it could look l
* **Show raw data only**: In normal use, the measured raw data is provided to the Sensirion VOCGasIndexAlgorithm, and also NOxGasIndexAlgorithm for SGP41, to get an index for the measured values in the range 1..500. If you want to use the raw value(s) from the sensor to apply another, or no, algorithm on it, this checkbox can be enabled. The indexed values won't be available when enabled.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P150.rst b/docs/source/Plugin/P150.rst
index a13450f8d..120713fea 100644
--- a/docs/source/Plugin/P150.rst
+++ b/docs/source/Plugin/P150.rst
@@ -130,10 +130,7 @@ Output
* **Log low-level values (INFO)**: When checked, the values during the actual read (max. 1 per second) will be output to the serial log, on INFO level. This can be used for debugging purposes. May not be available in all builds to save space in the bin file.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower than the **Continuous conversion cycle time**, the interval will return the previously measured value until an actual measurement has been retrieved from the sensor.
diff --git a/docs/source/Plugin/P151.rst b/docs/source/Plugin/P151.rst
index 04c3dc66d..eaf295578 100644
--- a/docs/source/Plugin/P151.rst
+++ b/docs/source/Plugin/P151.rst
@@ -87,6 +87,7 @@ Thus for this purpose the ``Pressure Max`` should be set to ``1000``.
:alt: Device configuration
+.. include:: DataAcquisition.repl
Change log
diff --git a/docs/source/Plugin/P153.rst b/docs/source/Plugin/P153.rst
index 1e45f2d82..b491cfc6e 100644
--- a/docs/source/Plugin/P153.rst
+++ b/docs/source/Plugin/P153.rst
@@ -95,10 +95,7 @@ Activating the heater helps to remove possible condensation from the sensor.
*High resolution*: Execute only high resolution measurements. (Default)
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower or equal than the required 10 * Heater time, the plugin will not start!
diff --git a/docs/source/Plugin/P154.rst b/docs/source/Plugin/P154.rst
index 40bf45fb7..eda33aa06 100644
--- a/docs/source/Plugin/P154.rst
+++ b/docs/source/Plugin/P154.rst
@@ -75,10 +75,7 @@ Device Settings
* **Detected Sensor Type**: Shows either ``BMP38x`` or ``BMP390`` or a number if no sensor or an unknown sensor ID is detected.
* **Altitude**: Optionally set the offset (in meters) of the sensor to convert pressure measurements to sea level pressure.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower or equal than the required 10 * Heater time, the plugin will not start!
diff --git a/docs/source/Plugin/P159.rst b/docs/source/Plugin/P159.rst
index f754d4b23..30fd97358 100644
--- a/docs/source/Plugin/P159.rst
+++ b/docs/source/Plugin/P159.rst
@@ -145,10 +145,7 @@ If the **Modify sensor settings** checkbox is checked when submitting the page,
Might the configuration not be set correctly, and changing them again doesn't seem to work, then resetting the sensor, by issuing the ``ld2410,factoryreset`` command while the task is enabled and the sensor connected, the factory defaults will be restored. (See the list of supported commands, below.)
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. as this is optional for this device. The data will be collected and optionally sent to any configured controllers using this interval. For sending the values to the enabled controller(s), the command ``TaskRun,`` can be used.
diff --git a/docs/source/Plugin/P162.rst b/docs/source/Plugin/P162.rst
index bad15fc4f..12417c60d 100644
--- a/docs/source/Plugin/P162.rst
+++ b/docs/source/Plugin/P162.rst
@@ -66,10 +66,7 @@ These settings are available for both W0 and W1. NB: The W1 settings can't be a
* **Send values on change**: When checked, and Interval set to 0, events will only be generated, and sent to any configured Controllers, when a Value is changed. On startup, the initial values will be sent out, but that's the default behavior for ESPEasy.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will not be set. The values will be optionally sent to any configured controllers using the interval, when set.
diff --git a/docs/source/Plugin/P163.rst b/docs/source/Plugin/P163.rst
index a83bfc02a..f5de42010 100644
--- a/docs/source/Plugin/P163.rst
+++ b/docs/source/Plugin/P163.rst
@@ -72,10 +72,7 @@ Device Settings
.. note:: To be able to use the Count-average value, the **Stats** checkbox for the Count value must be enabled. Only the available values will be used for the average, as data from any previous run is not persisted after plugin restart.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P164.rst b/docs/source/Plugin/P164.rst
index 49de70f7a..bb7223afc 100644
--- a/docs/source/Plugin/P164.rst
+++ b/docs/source/Plugin/P164.rst
@@ -89,10 +89,7 @@ Note that if either one of the tasks is set to **Not Set** compensation is switc
The ENS160 is sold on popular websites on a board including the AHT21 temperature and humidity sensor. This sensor can be used for the compensation algorithm of the ENS160.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower or equal than the required 10 * Heater time, the plugin will not start!
diff --git a/docs/source/Plugin/P166.rst b/docs/source/Plugin/P166.rst
index 8d9941a5f..b1ed23e58 100644
--- a/docs/source/Plugin/P166.rst
+++ b/docs/source/Plugin/P166.rst
@@ -80,10 +80,7 @@ Preset values
When leaving the *Name* field empty, that preset will not be saved, thus effectively deleted from the list.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced).
diff --git a/docs/source/Plugin/P167.rst b/docs/source/Plugin/P167.rst
index e058ae85a..fc9cca40d 100644
--- a/docs/source/Plugin/P167.rst
+++ b/docs/source/Plugin/P167.rst
@@ -102,10 +102,7 @@ N.B.: NOx is only available when a Sensirion SEN55 is installed, but is always s
N.B.2: The selected options determine the names of the output Values.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P168.rst b/docs/source/Plugin/P168.rst
index d2eaa10cc..66e8eaf9a 100644
--- a/docs/source/Plugin/P168.rst
+++ b/docs/source/Plugin/P168.rst
@@ -82,10 +82,7 @@ Device Settings
.. image:: P168_PowerSaveModeOptions.png
:alt: Power Save Mode options
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P169.rst b/docs/source/Plugin/P169.rst
index 4d0b9bfee..593a20180 100644
--- a/docs/source/Plugin/P169.rst
+++ b/docs/source/Plugin/P169.rst
@@ -327,10 +327,7 @@ Current Sensor Data
^^^^^^^^^^^^^^^^^^^
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced).
diff --git a/docs/source/Plugin/P170.rst b/docs/source/Plugin/P170.rst
index 0cc265861..f6879df71 100644
--- a/docs/source/Plugin/P170.rst
+++ b/docs/source/Plugin/P170.rst
@@ -65,10 +65,7 @@ When a Trigger is set to 0 it will be disabled. Rules have to be enabled to be a
* **Log signal level**: When enabled will log at Info level the received data from the sensor after reading. Can be used to find a suitable **Sensitivity** setting when the default doesn't work as expected. Should best be disabled during normal operation.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced).
diff --git a/docs/source/Plugin/P172.rst b/docs/source/Plugin/P172.rst
index 75805fc9e..0d6c6e7e1 100644
--- a/docs/source/Plugin/P172.rst
+++ b/docs/source/Plugin/P172.rst
@@ -65,10 +65,7 @@ Device Settings
* **Detected Sensor Type**: Shows either ``BMP38x`` or ``BMP390`` or a number if an unknown sensor ID is detected. (Only shown when the plugin is active.)
* **Altitude**: Optionally set the offset from sea level (in meters) of the sensor to convert pressure measurements to sea level pressure.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower or equal than the required 10 * Heater time, the plugin will not start!
diff --git a/docs/source/Plugin/P173.rst b/docs/source/Plugin/P173.rst
index be11b4ffd..f52649c42 100644
--- a/docs/source/Plugin/P173.rst
+++ b/docs/source/Plugin/P173.rst
@@ -47,10 +47,7 @@ Device Settings
* **Read in Low-power mode**: When enabled will use the Low-power version of the command to read the data, for improved battery-life when used in a bettery-powerd setup.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval.
diff --git a/docs/source/Plugin/P175.rst b/docs/source/Plugin/P175.rst
index a164346f1..de3a1c164 100644
--- a/docs/source/Plugin/P175.rst
+++ b/docs/source/Plugin/P175.rst
@@ -246,6 +246,8 @@ In the example setup, a GPIO pin should be connected to the ``SET`` input of the
Unlike the serial connected PMSx003 sensors, no sleep/wake command can be sent to the sensor, so sleep/wake can only be achieved by using the **SET** pin.
+.. include:: DataAcquisition.repl
+
Commands available
^^^^^^^^^^^^^^^^^^
diff --git a/docs/source/Plugin/P176.rst b/docs/source/Plugin/P176.rst
index 30a0620ac..be2209716 100644
--- a/docs/source/Plugin/P176.rst
+++ b/docs/source/Plugin/P176.rst
@@ -65,10 +65,7 @@ Logging
* **Quiet logging (reduces logging)**: During normal operation there is some minimal logging available, informing about successfully received packets. This logging is suppressed when Quiet logging is enabled.
-Data Acquisition
-^^^^^^^^^^^^^^^^
-
-This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured.
+.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 0 sec. In this situation, no data is sent automatically, and no events are generated. The data will still be collected and can be retrieved for use on a display, or further processing from other rules. When an Interval is set the data can be optionally sent to any configured controllers and either a single ``#All`` event or an event per value will be generated. As an alternative, the ``TaskRun`` command can be used to publish the data at any moment.
diff --git a/docs/source/Reference/Flashing.rst b/docs/source/Reference/Flashing.rst
index 26bbb5cd5..5c0034d54 100644
--- a/docs/source/Reference/Flashing.rst
+++ b/docs/source/Reference/Flashing.rst
@@ -28,6 +28,10 @@ The included tools are all for Windows.
For Linux or Mac users, the recommended tool for ESP82xx/ESP32 is `esptool.py `_
+Platform independent software in your browser (works also on Linux):
+
+* `ESPWebTool `_ just use Chrome or Edge browser to flash usb connected ESP82xx/ESP32 board. For Firefox you need to install extra `addon `_.
+
Flashing Parameters
===================
@@ -95,6 +99,7 @@ ESP82xx
ESPEasy builds based on esp8266/Arduino library version 2.7.x and later can handle compressed ``.bin.gz`` files.
When in doubt, just use the ``.bin`` files.
+If first time flash use ``.bin`` file at address 0.
ESP32
=====
diff --git a/docs/source/Reference/SystemVariable.rst b/docs/source/Reference/SystemVariable.rst
index 211078b20..308b38676 100644
--- a/docs/source/Reference/SystemVariable.rst
+++ b/docs/source/Reference/SystemVariable.rst
@@ -175,12 +175,22 @@ More uses of these system variables can be seen in the rules section and formula
- +0100
- System time-zone offset from UTC, using ``[+|-]HHMM`` format, + or -, hours and minutes both in 2 digits, zero-prefixed. Does take DST into account.
-
+ * - ``%systzoffset_s%``
+ - 3600
+ - System time-zone offset from UTC in seconds, + or -. Does take DST into account. (Added: 2025/06/01, not available in Limited builds)
+ -
* - ``%unixtime%``
- 1521731277
- Unix time (seconds since epoch, 1970-01-01 00:00:00)
Example: 1521731277 = 2018-03-22 15:07:57
- Yes
+ * - ``%unixtime_lcl%``
+ - 1748813303
+ - Local Unix time (seconds since epoch, 1970-01-01 00:00:00) with Time-zone and DST applied. (Added: 2025/06/01, not available in Limited builds)
+
+ Example: 1748813303 = 2025-06-01 21:28:32
+ - Yes
* - ``%uptime%``
- 3244
- Uptime in minutes.
@@ -362,12 +372,22 @@ The conversion always outputs a string, but not all of these can be converted ba
* - Secs to dhms: ``%c_s2dhms%(100000)``
- Secs to dhms: ``1d03:46:40``
- Seconds to days/hours/minutes/seconds notation
+ * - Unix Timestamp to date/time: ``%c_ts2date%(1748813303)``
+ - Unix Timestamp to date/time: ``2025-06-01 21:28:32``
+ - Unix Timestamp to year-month-day hour:minute:second notation (Default ``%unixtime%`` is UTC)
+
+ Optionally takes a second argument <> 0 to use AM/PM time notation.
+
+ ``%c_ts2date%(1748813303, 1)`` -> ``2025-06-01 09:28:32 PM``
+ * - Unix Timestamp to weekday: ``%c_ts2wday%(%unixtime_lcl%)``
+ - Unix Timestamp to weekday: ``2``
+ - Return the numeric weekday (dow) index, 0..6 (sun..sat), so it can be used with f.e. ``{lookup:%c_ts2wday%(%unixtime_lcl%):3:sunmontuewedthufrisat}`` to show a 3 character day name, that can easily be translated into your desired languag, and the length adjusted as desired.
* - Random(L,H): ``%c_random%(0, 1)``
- Random(L,H): ``0.123``
- Generate random number in the given range L ... H (Added: 2025/04/29)
- * - To HEX: ``%c_2hex%(100000)``
- - To HEX: ``186A0``
- - Convert integer value to HEX notation. (Added: 2020/10/07)
+ * - To HEX: ``%c_2hex%(100000[,])``
+ - To HEX: ``186A0`` or with minDigits = 6 ``0186A0``
+ - Convert integer value to HEX notation. (Added: 2020/10/07, 2025/05/25: optional minDigits)
* - Unit to IP: ``%c_u2ip%(%unit%,0)``
- Unit to IP: ``192.168.1.67``
- Convert a (known) unit number to its IP Address. (Added: 2020/11/08)
@@ -415,7 +435,59 @@ The conversion always outputs a string, but not all of these can be converted ba
- Convert to the ESP-Type (string) of the remote unit. (Added: 2024/04/21)
See ``%c_utype%()`` for the names and numbers used.
+ * - Check if numeric value (test:'123'): ``%c_isnum%(test)``
+ - Check if numeric value (test:'123'): ``1``
+ - Check if the content of a string variable (set with ``LetStr`` command) contains a numeric value. 0 = false, 1 = true
+ (Added: 2025/05/25, only available when String Variables feature is included in the build)
+
+ * - Format (testf:'Out $6.2f M$g'): ``%c_strf%(testf,123.45,2)``
+ - Format 1 or 2 values using a format specification.
+ - For formatting values, similar to C ``printf`` function, only supporting floating point formatting specifiers, and using ``$`` instead of the '%' trigger character used by printf.
+
+ (Added: 2025/05/27, only available when String Variables feature is included in the build)
+
+ Supported format specs: A string variable, containing 1 or 2 format specifiers. This format string must be set using the ``LetStr`` command in a string variable. Or alternatively, a direct format string can be provided, that must *not* be quoted, and *not* contain any commas.
+
+ ``$[flags][width][.precision]specifier``
+
+ ``specifier``:
+
+ * ``f``: Decimal floating point, lowercase
+ * ``F``: Decimal floating point, uppercase
+ * ``e``: Scientific notation (mantissa/exponent), lowercase
+ * ``E``: Scientific notation (mantissa/exponent), uppercase
+ * ``g``: Use the shortest representation, $e or $f
+ * ``G``: Use the shortest representation, $E or $F
+ * ``%``: Output a percent sign (this can also be achieved by using ``%%`` where the '%' is intended)
+
+ ``flags``:
+
+ * ``-``: Left-justify within the given field width
+ * ``+``: Forces to preceed the result with a plus or minus sign (+ or -) even for positive numbers
+ * ``#``: Forces the output to contain a decimal point even if no more digits follow
+ * ``0``: Left-pads the number with zeroes (0) instead of spaces when padding is specified
+
+ ``width``:
+
+ * ``(number)``: Minimum number of characters to be output, padded with spaces unless ``0`` flag is specified, not truncated when the output is wider
+
+ ``.precision``:
+
+ * ``.(number)``: For ``e``, ``E``, ``f`` and ``F`` specifiers, this is the number of digits to be printed after the decimal point (by default, this is 6).
+ * ``.(number)``: For ``g`` and ``G`` specifiers, this is the maximum number of significant digits to be printed.
+
+ (This is a summary of the C ``printf`` specification, adjusted for this function. Other printf features are not, or only partially, supported!)
+
+ **Examples**:
+
+ Output a value with minimal decimals: ``$g`` This can be used for output of integer values as all ESPEasy internal values are of floating point nature.
+
+ Output a temperature with 1 decimal: ``$.1f {D}C``
+
+ Output humidity with a percent sign: ``$g$% RH`` (remove spaces to preserve some display-space)
+
+ .. warning:: If more than 2, or not listed here, format specifiers are used in the format string, then this may cause unexpected behavior of the ESP, because of out-of-bounds memory access!
Task Formulas
^^^^^^^^^^^^^
diff --git a/docs/source/Reference/TaskValueSetPresentation_BME280.png b/docs/source/Reference/TaskValueSetPresentation_BME280.png
new file mode 100644
index 000000000..100abba62
Binary files /dev/null and b/docs/source/Reference/TaskValueSetPresentation_BME280.png differ
diff --git a/docs/source/Reference/TaskValueSetPresentation_LookUp.png b/docs/source/Reference/TaskValueSetPresentation_LookUp.png
new file mode 100644
index 000000000..c57a2a2e1
Binary files /dev/null and b/docs/source/Reference/TaskValueSetPresentation_LookUp.png differ
diff --git a/docs/source/Reference/TaskValueSetPresentation_Sysinfo.png b/docs/source/Reference/TaskValueSetPresentation_Sysinfo.png
new file mode 100644
index 000000000..a2cba62a2
Binary files /dev/null and b/docs/source/Reference/TaskValueSetPresentation_Sysinfo.png differ
diff --git a/docs/source/Rules/Rules.rst b/docs/source/Rules/Rules.rst
index 8afb71299..6e5ddd907 100644
--- a/docs/source/Rules/Rules.rst
+++ b/docs/source/Rules/Rules.rst
@@ -256,6 +256,26 @@ As of mega-201803.. we have the possibility to use AND/OR:
Up to two AND/OR can be used per if statement, that means that you can test
three float values and if the statement is true/false corresponding action will take place.
+String comparisons
+------------------
+
+Added: 2025-05-25
+
+Support for string variables also supports the use of string comparisons with the ``if`` statement. String comparisons are **case-sensitive**, so if non-case-sensitive comparisons are needed, using ``[str##V#l]`` will output the content in lower-case (without changing the stored content).
+
+.. code-block:: none
+
+ on zigbee2mqtt/eria_dimswitch_1#action do
+ LetStr,onoff,[zigbee2mqtt/eria_dimswitch_1#action]
+ if [str#onoff#V#l]=on
+ gpio,12,1 // on
+ else
+ gpio,12,0 // off
+ endif
+ endon
+
+(Example adapted from :ref:`P037_page`)
+
Trigger
-------
@@ -756,6 +776,20 @@ Added: 2023-12-01
Short-hand notation can be nested like this: ``[int#%v%v7%%]`` or use simple calculations like this: ``[int#%v=7+%v100%%]``
This allows to simply switch a number of variable offsets in rules by only changing 1 variable.
+String variables
+----------------
+
+Added: 2025-05-25: Support for String variables. (ESP32 builds only, because of memory restictions on ESP8266 platform)
+
+To store string/text data in internal variables, a new command ``LetStr,,`` has been introduced. These variables are stored independent from the numeric values that are stored with the ``Let`` command.
+
+For using the content of a string variable, ``[str#]`` should be used. This form also supports formatting and justification options, described below.
+
+If the value contains spaces or commas, the ```` must be wrapped in quotes to store all data. String concatenation can be achieved by assigning the value to the same variable, like ``LetStr,test,"[str#test] extra text containing spaces appended to variable 'test'"``. The space before ``extra`` is *also* included in the new content.
+
+A string variable that was not set to any value is assumed to hold the pseudo-value of an empty string.
+
+For determining the length of the current content of a string variable, ``[length#]`` can be used. Formatting and justification can be applied if desired.
@@ -1005,6 +1039,47 @@ For example (bit useless though, just for illustrative purposes):
221351 : Info : Command: logentry
221353 : Info : 87
+Translate the current day to a localized (German) 3 character abbreviation: (using named variables)
+
+.. code-block:: none
+
+ Let,idx,(%sysweekday%-1)*3 // 3 characters per value
+ Let,idx3,%v_idx%+3 // 3 characters to display
+ [DSPLeft].7dtext,{substring:%v_idx%:%v_idx3%:SONMONDIEMITDONFRESAM}~%syshour_0%.%sysmin_0%
+
+Or using 2-character Dutch abbreviations:
+
+.. code-block:: none
+
+ Let,idx,(%sysweekday%-1)*2 // 2 characters per value
+ Let,idx2,%v_idx%+2 // 2 characters to display
+ [DSPLeft].7dtext,{substring:%v_idx%:%v_idx2%:"ZOMADIWODOVRZA"}~%syshour_0%.%sysmin_0%
+
+Translate the current month to a Polish 3 character abbreviation, format DD MMM YY :
+
+.. code-block:: none
+
+ Let,idx,(%sysmonth%-1)*3 // 3 characters per value
+ Let,idx3,%v_idx%+3 // 3 characters to display
+ [DSPLeft].7dtext,%sysday_0%~{substring:%v_idx%:%v_idx3%:STYLUTMARKWIMAJCZELIPSIEWRZPAZLISGRU}~%sysyears%
+
+NB: Using all uppercase here as that shows most readable on a 7-segment display, for other purposes, CamelCase/lowercase can of course be used.
+
+Lookup
+^^^^^^
+
+(Added: 2025/05/29, only available when String Variables feature is included in the build)
+
+With ``lookup`` you can look up a substring value, based on an index and a length, from a longer string, somewhat similar to ``substring``.
+
+Usage: ``{lookup:::}``
+
+````: The positive 0-based index value. If the index indicates a value out of range, the entire calculation is returned unaltered.
+
+````: The length of the value to retrieve. This implies that all values to lookup are required to have the same length. Shorter lookup values should be padded with spaces or another character to that length.
+
+````: The combined string with all lookup values padded to have the same length, f.e. ``"Off.Fan HeatCool"``, where all lookup values are of length 4. The index to retrieve the word ``Off.`` is 0, ``Fan`` (including a space) is index 1, etc. This string should be wrapped in quotes if it contains space or colon ``:`` character(s).
+
IndexOf and IndexOf_ci
^^^^^^^^^^^^^^^^^^^^^^
diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp b/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp
index 208729b47..f0494dd85 100644
--- a/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp
+++ b/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp
@@ -198,6 +198,14 @@ static const uint8_t PROGMEM
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
0x00, 0x00, // XSTART = 0
320 >> 8, 320 & 0xFF }, // XEND = 320
+ Rcmd2black170x320[] = { // 7735R init, part 2 (mini 170x320)
+ 2, // 2 commands in list:
+ ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
+ 0x00, 0x00, // XSTART = 0
+ 0x00, 170, // XEND = 170
+ ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
+ 0x00, 0x00, // XSTART = 0
+ 320 >> 8, 320 & 0xFF }, // XEND = 320
#endif // if ST7735_EXTRA_INIT
Rcmd3[] = { // 7735R init, part 3 (red or green tab)
@@ -277,6 +285,13 @@ void Adafruit_ST7735::initR(uint8_t options) {
const uint8_t data = 0x00;
sendCommand(ST77XX_INVON, &data, 0);
sendCommand(ST77XX_INVON, &data, 0); // Write twice...
+ } else if (options == INITR_BLACKTAB170x320) {
+ _height = ST7735_TFTHEIGHT_320;
+ _width = ST7735_TFTWIDTH_170;
+ displayInit(Rcmd2black170x320);
+ const uint8_t data = 0x00;
+ sendCommand(ST77XX_INVON, &data, 0);
+ sendCommand(ST77XX_INVON, &data, 0); // Write twice...
#endif // if ST7735_EXTRA_INIT
} else {
// colstart, rowstart left at default '0' values
@@ -324,7 +339,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB;
#if ST7735_EXTRA_INIT
- } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320)) {
+ } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) {
madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB;
#endif // if ST7735_EXTRA_INIT
} else {
@@ -348,6 +363,11 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
_width = ST7735_TFTWIDTH_172;
_colstart = 34;
_rowstart = 1;
+ } else if (tabcolor == INITR_BLACKTAB170x320) {
+ _height = ST7735_TFTHEIGHT_320;
+ _width = ST7735_TFTWIDTH_170;
+ _colstart = 35;
+ _rowstart = 0;
#endif // if ST7735_EXTRA_INIT
} else {
_height = ST7735_TFTHEIGHT_160;
@@ -360,7 +380,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB;
#if ST7735_EXTRA_INIT
- } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320)) {
+ } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) {
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB;
#endif // if ST7735_EXTRA_INIT
} else {
@@ -384,6 +404,11 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
_height = ST7735_TFTWIDTH_172;
_colstart = 34;
_rowstart = 1;
+ } else if (tabcolor == INITR_BLACKTAB170x320) {
+ _width = ST7735_TFTHEIGHT_320;
+ _height = ST7735_TFTWIDTH_170;
+ _colstart = 35;
+ _rowstart = 0;
#endif // if ST7735_EXTRA_INIT
} else {
_width = ST7735_TFTHEIGHT_160;
@@ -396,7 +421,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
madctl = ST77XX_MADCTL_RGB;
#if ST7735_EXTRA_INIT
- } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320)) {
+ } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) {
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB;
#endif // if ST7735_EXTRA_INIT
} else {
@@ -420,6 +445,11 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
_width = ST7735_TFTWIDTH_172;
_colstart = 34;
_rowstart = 1;
+ } else if (tabcolor == INITR_BLACKTAB170x320) {
+ _height = ST7735_TFTHEIGHT_320;
+ _width = ST7735_TFTWIDTH_170;
+ _colstart = 35;
+ _rowstart = 0;
#endif // if ST7735_EXTRA_INIT
} else {
_height = ST7735_TFTHEIGHT_160;
@@ -432,7 +462,7 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) {
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB;
#if ST7735_EXTRA_INIT
- } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320)) {
+ } else if ((tabcolor == INITR_BLACKTAB135x240) || (tabcolor == INITR_BLACKTAB172x320) || (tabcolor == INITR_BLACKTAB170x320)) {
madctl = ST77XX_MADCTL_RGB;
#endif // if ST7735_EXTRA_INIT
} else {
@@ -456,6 +486,11 @@ void Adafruit_ST7735::setRotation(uint8_t m) {
_height = ST7735_TFTWIDTH_172;
_colstart = 34;
_rowstart = 1;
+ } else if (tabcolor == INITR_BLACKTAB170x320) {
+ _width = ST7735_TFTHEIGHT_320;
+ _height = ST7735_TFTWIDTH_170;
+ _colstart = 35;
+ _rowstart = 0;
#endif // if ST7735_EXTRA_INIT
} else {
_width = ST7735_TFTHEIGHT_160;
diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7735.h b/lib/Adafruit_ST77xx/Adafruit_ST7735.h
index 7be3dc88d..8ad70e7c0 100644
--- a/lib/Adafruit_ST77xx/Adafruit_ST7735.h
+++ b/lib/Adafruit_ST77xx/Adafruit_ST7735.h
@@ -32,6 +32,7 @@
#define INITR_GREENTAB160x80 0x06
#define INITR_BLACKTAB135x240 0x07
#define INITR_BLACKTAB172x320 0x08
+#define INITR_BLACKTAB170x320 0x09
// Some register settings
#define ST7735_MADCTL_BGR 0x08
diff --git a/lib/Adafruit_ST77xx/Adafruit_ST77xx.h b/lib/Adafruit_ST77xx/Adafruit_ST77xx.h
index 08f928565..e3af093d4 100644
--- a/lib/Adafruit_ST77xx/Adafruit_ST77xx.h
+++ b/lib/Adafruit_ST77xx/Adafruit_ST77xx.h
@@ -35,6 +35,7 @@
#define ST7735_TFTWIDTH_80 80 // for mini
#define ST7735_TFTWIDTH_135 135
#define ST7735_TFTWIDTH_172 172
+#define ST7735_TFTWIDTH_170 170
#define ST7735_TFTHEIGHT_128 128 // for 1.44" display
#define ST7735_TFTHEIGHT_160 160 // for 1.8" and mini display
#define ST7735_TFTHEIGHT_240 240
diff --git a/src/Custom-sample.h b/src/Custom-sample.h
index d25a5a38e..30f8b109e 100644
--- a/src/Custom-sample.h
+++ b/src/Custom-sample.h
@@ -248,6 +248,7 @@
// #define FEATURE_ANYRTTTL_LIB 1 // Use AnyRttl library for RTTTL handling
// #define FEATURE_ANYRTTTL_ASYNC 1 // When AnyRttl enabled, use Async (nonblocking) mode instead of the default Blocking mode
// #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense
+// #define FEATURE_STRING_VARIABLES 1 // Enable String variable support (enabled on ESP32, NOT supported on ESP8266 for memory restrictions!)
#if FEATURE_CUSTOM_PROVISIONING
// For device models, see src/src/DataTypes/DeviceModel.h
diff --git a/src/_P021_Level.ino b/src/_P021_Level.ino
index 8a9a45ec1..57c4c3ab7 100644
--- a/src/_P021_Level.ino
+++ b/src/_P021_Level.ino
@@ -234,13 +234,14 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string)
// FormSelector with all operation mode options
const __FlashStringHelper *options[] = { F("Classic"), F("Off"), F("Standby"), F("On"), F("Local"), F("Remote") };
+
/*
- const int optionValues[] =
- { P021_OPMODE_CLASSIC, P021_OPMODE_OFF, P021_OPMODE_STANDBY, P021_OPMODE_ON, P021_OPMODE_TEMP, P021_OPMODE_REMOTE };
- */
+ const int optionValues[] =
+ { P021_OPMODE_CLASSIC, P021_OPMODE_OFF, P021_OPMODE_STANDBY, P021_OPMODE_ON, P021_OPMODE_TEMP, P021_OPMODE_REMOTE };
+ */
constexpr size_t optionCount = NR_ELEMENTS(options);
- const FormSelectorOptions selector(optionCount, options/*, optionValues*/);
- selector.addFormSelector(F("Control mode"), F(P021_GUID_OPMODE), P021_OPMODE);
+ const FormSelectorOptions selector(optionCount, options /*, optionValues*/);
+ selector.addFormSelector(F("Control mode"), F(P021_GUID_OPMODE), P021_OPMODE);
// Add timer values depending on build size
// - minimum build size: units are always in seconds; drop the units on the form
@@ -803,10 +804,11 @@ void P021_evaluate(struct EventStruct *event)
break;
}
+ relay_output ^= bitRead(P021_FLAGS, P021_INV_OUTPUT); // Invert when selected
+
// Actuate the output pin taking output invert flag into account
if (validGpio(P021_GPIO_RELAY))
{
- relay_output ^= bitRead(P021_FLAGS, P021_INV_OUTPUT); // Invert when selected
digitalWrite(P021_GPIO_RELAY, relay_output ? HIGH : LOW);
}
diff --git a/src/_P116_ST77xx.ino b/src/_P116_ST77xx.ino
index d49802986..d537bc249 100644
--- a/src/_P116_ST77xx.ino
+++ b/src/_P116_ST77xx.ino
@@ -8,6 +8,8 @@
// History:
+// 2025-06-11 tonhuisman: Add support for ST7789v3/ST7735 display with 170x320 resolution
+// 2025-02-20 tonhuisman: Add support for ST7735 display with 172x320 resolution
// 2024-05-04 tonhuisman: Add Default font selection setting, if AdafruitGFX_Helper fonts are included
// 2024-03-17 tonhuisman: Add support for another alternative initialization for ST7735 displays, as the display controller
// used on the LilyGO TTGO T-Display (16 MB) seems to be a ST7735, despite being documented as ST7789
@@ -149,6 +151,7 @@ boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string)
# if P116_EXTRA_ST7735
ST77xx_type_toString(ST77xx_type_e::ST7735s_135x240),
ST77xx_type_toString(ST77xx_type_e::ST7735s_172x320),
+ ST77xx_type_toString(ST77xx_type_e::ST77xxs_170x320),
# endif // if P116_EXTRA_ST7735
ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x320),
ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x240),
@@ -169,6 +172,7 @@ boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string)
# if P116_EXTRA_ST7735
static_cast(ST77xx_type_e::ST7735s_135x240),
static_cast(ST77xx_type_e::ST7735s_172x320),
+ static_cast(ST77xx_type_e::ST77xxs_170x320),
# endif // if P116_EXTRA_ST7735
static_cast(ST77xx_type_e::ST7789vw_240x320),
static_cast(ST77xx_type_e::ST7789vw_240x240),
diff --git a/src/src/Commands/InternalCommands.cpp b/src/src/Commands/InternalCommands.cpp
index 5bef61dfc..47040b07a 100644
--- a/src/src/Commands/InternalCommands.cpp
+++ b/src/src/Commands/InternalCommands.cpp
@@ -326,6 +326,9 @@ bool InternalCommands::executeInternalCommand()
case ESPEasy_cmd_e::jsonportstatus: COMMAND_CASE_A(Command_JSONPortStatus, -1); // Diagnostic.h
#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS
case ESPEasy_cmd_e::let: COMMAND_CASE_A(Command_Rules_Let, 2); // Rules.h
+ #if FEATURE_STRING_VARIABLES
+ case ESPEasy_cmd_e::letstr: COMMAND_CASE_A(Command_Rules_LetStr, 2); // Rules.h
+ #endif // if FEATURE_STRING_VARIABLES
case ESPEasy_cmd_e::load: COMMAND_CASE_A(Command_Settings_Load, 0); // Settings.h
case ESPEasy_cmd_e::logentry: COMMAND_CASE_A(Command_logentry, -1); // Diagnostic.h
case ESPEasy_cmd_e::looptimerset: COMMAND_CASE_A(Command_Loop_Timer_Set, 3); // Timers.h
@@ -450,6 +453,10 @@ bool InternalCommands::executeInternalCommand()
case ESPEasy_cmd_e::taskrun: COMMAND_CASE_A(Command_Task_Run, 1); // Tasks.h
case ESPEasy_cmd_e::taskrunat: COMMAND_CASE_A(Command_Task_Run, 2); // Tasks.h
case ESPEasy_cmd_e::taskvalueset: COMMAND_CASE_A(Command_Task_ValueSet, 3); // Tasks.h
+ #if FEATURE_STRING_VARIABLES
+ case ESPEasy_cmd_e::taskvaluesetderived: COMMAND_CASE_A(Command_Task_ValueSetDerived, 4); // Tasks.h
+ case ESPEasy_cmd_e::taskvaluesetpresentation: COMMAND_CASE_A(Command_Task_ValueSetPresentation, 3); // Tasks.h
+ #endif // if FEATURE_STRING_VARIABLES
case ESPEasy_cmd_e::taskvaluetoggle: COMMAND_CASE_A(Command_Task_ValueToggle, 2); // Tasks.h
case ESPEasy_cmd_e::taskvaluesetandrun: COMMAND_CASE_A(Command_Task_ValueSetAndRun, 3); // Tasks.h
case ESPEasy_cmd_e::timerpause: COMMAND_CASE_A(Command_Timer_Pause, 1); // Timers.h
diff --git a/src/src/Commands/InternalCommands_decoder.cpp b/src/src/Commands/InternalCommands_decoder.cpp
index 9bc386a1e..8074dd70c 100644
--- a/src/src/Commands/InternalCommands_decoder.cpp
+++ b/src/src/Commands/InternalCommands_decoder.cpp
@@ -107,6 +107,9 @@ const char Internal_commands_fghij[] PROGMEM =
#define Int_cmd_l_offset ESPEasy_cmd_e::let
const char Internal_commands_l[] PROGMEM =
"let|"
+ #if FEATURE_STRING_VARIABLES
+ "letstr|"
+ #endif
"load|"
"logentry|"
"looptimerset|"
@@ -248,6 +251,10 @@ const char Internal_commands_t[] PROGMEM =
"taskrun|"
"taskrunat|"
"taskvalueset|"
+ #if FEATURE_STRING_VARIABLES
+ "taskvaluesetderived|"
+ "taskvaluesetpresentation|"
+ #endif // if FEATURE_STRING_VARIABLES
"taskvaluetoggle|"
"taskvaluesetandrun|"
"timerpause|"
diff --git a/src/src/Commands/InternalCommands_decoder.h b/src/src/Commands/InternalCommands_decoder.h
index 32eed02c7..88f86351e 100644
--- a/src/src/Commands/InternalCommands_decoder.h
+++ b/src/src/Commands/InternalCommands_decoder.h
@@ -80,6 +80,9 @@ enum class ESPEasy_cmd_e : uint8_t {
#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS
let,
+ #if FEATURE_STRING_VARIABLES
+ letstr,
+ #endif // if FEATURE_STRING_VARIABLES
load,
logentry,
looptimerset,
@@ -204,6 +207,10 @@ enum class ESPEasy_cmd_e : uint8_t {
taskrun,
taskrunat,
taskvalueset,
+ #if FEATURE_STRING_VARIABLES
+ taskvaluesetderived,
+ taskvaluesetpresentation,
+ #endif // if FEATURE_STRING_VARIABLES
taskvaluetoggle,
taskvaluesetandrun,
timerpause,
diff --git a/src/src/Commands/Rules.cpp b/src/src/Commands/Rules.cpp
index fcfb66797..cbf2471e9 100644
--- a/src/src/Commands/Rules.cpp
+++ b/src/src/Commands/Rules.cpp
@@ -88,6 +88,24 @@ const __FlashStringHelper * Command_Rules_Let(struct EventStruct *event, const c
return return_command_failed_flashstr();
}
+#if FEATURE_STRING_VARIABLES
+const __FlashStringHelper * Command_Rules_LetStr(struct EventStruct *event, const char *Line)
+{
+ String varName;
+ String TmpStr1;
+ GetArgv(Line, varName, 2);
+
+ if (!varName.isEmpty() &&
+ ExtraTaskSettings.checkInvalidCharInNames(varName.c_str()) && GetArgv(Line, TmpStr1, 3)) {
+ String result = parseTemplate(TmpStr1);
+
+ setCustomStringVar(varName, result);
+ return return_command_success_flashstr();
+ }
+ return return_command_failed_flashstr();
+}
+#endif // if FEATURE_STRING_VARIABLES
+
const __FlashStringHelper * Command_Rules_IncDec(struct EventStruct *event, const char *Line, const ESPEASY_RULES_FLOAT_TYPE factor)
{
String varName;
diff --git a/src/src/Commands/Rules.h b/src/src/Commands/Rules.h
index 03ca32187..5a111a23a 100644
--- a/src/src/Commands/Rules.h
+++ b/src/src/Commands/Rules.h
@@ -8,6 +8,9 @@ String Command_Rules_UseRules(struct EventStruct *event, const char *Line);
const __FlashStringHelper * Command_Rules_Async_Events(struct EventStruct *event, const char *Line);
const __FlashStringHelper * Command_Rules_Events(struct EventStruct *event, const char *Line);
const __FlashStringHelper * Command_Rules_Let(struct EventStruct *event, const char *Line);
+#if FEATURE_STRING_VARIABLES
+const __FlashStringHelper * Command_Rules_LetStr(struct EventStruct *event, const char *Line);
+#endif // if FEATURE_STRING_VARIABLES
const __FlashStringHelper * Command_Rules_Inc(struct EventStruct *event, const char *Line);
const __FlashStringHelper * Command_Rules_Dec(struct EventStruct *event, const char *Line);
diff --git a/src/src/Commands/Tasks.cpp b/src/src/Commands/Tasks.cpp
index 1ac31846c..55ef3fabd 100644
--- a/src/src/Commands/Tasks.cpp
+++ b/src/src/Commands/Tasks.cpp
@@ -238,6 +238,70 @@ const __FlashStringHelper * Command_Task_ValueSet(struct EventStruct *event, con
return taskValueSet(event, Line, taskIndex, success);
}
+#if FEATURE_STRING_VARIABLES
+const __FlashStringHelper * Command_Task_ValueSetDerived(struct EventStruct *event, const char *Line)
+{
+ return taskValueSetString(event, Line, F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), F(TASK_VALUE_UOM_PREFIX_TEMPLATE));
+}
+
+const __FlashStringHelper * Command_Task_ValueSetPresentation(struct EventStruct *event, const char *Line)
+{
+ return taskValueSetString(event, Line, F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE));
+}
+
+const __FlashStringHelper * taskValueSetString(struct EventStruct *event, const char *Line, const __FlashStringHelper * storageTemplate, const __FlashStringHelper * uomTemplate)
+{
+ String taskName;
+ taskIndex_t tmpTaskIndex = INVALID_TASK_INDEX;
+ if ((event->Par1 <= 0 || event->Par1 >= INVALID_TASK_INDEX) && GetArgv(Line, taskName, 2)) {
+ tmpTaskIndex = findTaskIndexByName(taskName, true);
+ if (tmpTaskIndex != INVALID_TASK_INDEX) {
+ event->Par1 = tmpTaskIndex + 1;
+ }
+ }
+ String valueName;
+ const bool hasValueName = GetArgv(Line, valueName, 3);
+ if ((event->Par2 <= 0 || event->Par2 >= VARS_PER_TASK) && event->Par1 - 1 != INVALID_TASK_INDEX && hasValueName)
+ {
+ uint8_t tmpVarNr = findDeviceValueIndexByName(valueName, event->Par1 - 1);
+ if (tmpVarNr != VARS_PER_TASK) {
+ event->Par2 = tmpVarNr + 1;
+ }
+ }
+
+ if (event->Par1 > 0 && validTaskIndex(event->Par1 - 1)) {
+ taskName = getTaskDeviceName(event->Par1 - 1); // Taskname must be valid
+ }
+
+ if (!hasValueName || (event->Par1 > 0 && validTaskIndex(event->Par1 - 1) && event->Par2 > 0 && validTaskVarIndex(event->Par2 - 1))) {
+ valueName = getTaskValueName(static_cast(event->Par1 - 1), event->Par2 - 1); // Convert numeric var index into name
+ }
+ // addLog(LOG_LEVEL_INFO, strformat(F("TaskValueSetStorage: task: %s (%d), value: %s (%d), Line: %s"), taskName.c_str(), event->Par1, valueName.c_str(), event->Par2, Line)); // FIXME
+
+ String argument;
+ if (!taskName.isEmpty() && !valueName.isEmpty() && GetArgv(Line, argument, 4)) {
+ taskName.toLowerCase();
+ String orgValueName(valueName);
+ valueName.toLowerCase();
+ String key = strformat(storageTemplate, taskName.c_str(), valueName.c_str());
+ const String key2 = strformat(F(TASK_VALUE_NAME_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str());
+ setCustomStringVar(key, argument);
+ if (getCustomStringVar(key2).isEmpty() || argument.isEmpty()) {
+ setCustomStringVar(key2, argument.isEmpty() ? EMPTY_STRING : orgValueName);
+ }
+ if (uomTemplate != nullptr) { // We have a Unit of Measure template
+ if (GetArgv(Line, argument, 5)) { // check for extra argument holding UoM
+ key = strformat(uomTemplate, taskName.c_str(), valueName.c_str());
+ setCustomStringVar(key, argument);
+ }
+ }
+ // addLog(LOG_LEVEL_INFO, strformat(F("TaskValueSetStorage: key: %s, argument: %s"), key.c_str(), argument.c_str())); // FIXME
+ return return_command_success_flashstr();
+ }
+ return return_command_failed_flashstr(); // taskValueSet(event, Line, taskIndex, success);
+}
+#endif
+
const __FlashStringHelper * Command_Task_ValueToggle(struct EventStruct *event, const char *Line)
{
taskIndex_t taskIndex;
diff --git a/src/src/Commands/Tasks.h b/src/src/Commands/Tasks.h
index c44c7d755..758f6f69e 100644
--- a/src/src/Commands/Tasks.h
+++ b/src/src/Commands/Tasks.h
@@ -12,6 +12,11 @@ const __FlashStringHelper * Command_Task_Enable(struct EventStruct *event, const
const __FlashStringHelper * Command_PriorityTask_Disable(struct EventStruct *event, const char *Line);
#endif
const __FlashStringHelper * Command_Task_ValueSet(struct EventStruct *event, const char* Line);
+#if FEATURE_STRING_VARIABLES
+const __FlashStringHelper * Command_Task_ValueSetDerived(struct EventStruct *event, const char *Line);
+const __FlashStringHelper * Command_Task_ValueSetPresentation(struct EventStruct *event, const char *Line);
+const __FlashStringHelper * taskValueSetString(struct EventStruct *event, const char *Line, const __FlashStringHelper * storageTemplate, const __FlashStringHelper * uomTemplate = nullptr);
+#endif // if FEATURE_STRING_VARIABLES
const __FlashStringHelper * Command_Task_ValueToggle(struct EventStruct *event, const char* Line);
const __FlashStringHelper * Command_Task_ValueSetAndRun(struct EventStruct *event, const char* Line);
const __FlashStringHelper * Command_ScheduleTask_Run(struct EventStruct *event, const char* Line);
diff --git a/src/src/Commands/i2c.cpp b/src/src/Commands/i2c.cpp
index b7ca59f61..d0e69777b 100644
--- a/src/src/Commands/i2c.cpp
+++ b/src/src/Commands/i2c.cpp
@@ -64,7 +64,7 @@ const __FlashStringHelper* Command_i2c_Scanner(struct EventStruct *event, const
}
#endif // if FEATURE_I2CMULTIPLEXER
} else {
- serialPrintln(F("I2C : Not enabled."));
+ serialPrintln(strformat(F("I2C %d: Not enabled."), i2cBus));
}
}
I2CSelectHighClockSpeed(0); // By default the bus is in standard speed
diff --git a/src/src/CustomBuild/ESPEasyDefaults.h b/src/src/CustomBuild/ESPEasyDefaults.h
index 5be4f2ae5..43cd8e2c5 100644
--- a/src/src/CustomBuild/ESPEasyDefaults.h
+++ b/src/src/CustomBuild/ESPEasyDefaults.h
@@ -469,6 +469,16 @@
#endif
+#if FEATURE_STRING_VARIABLES
+# define TASK_VALUE_DERIVED_PREFIX_TEMPLATE "_%s_%s-derived" // Includes a not-allowed varname character (-) to prevent undesired manipulation via LetStr
+# define TASK_VALUE_UOM_PREFIX_TEMPLATE "_%s_%s-uom"
+# define TASK_VALUE_NAME_PREFIX_TEMPLATE "_%s_%s-name"
+# define TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE "_%s_%s-presentation"
+# define TASK_VALUE_PRESENTATION_PREFIX_STRING "$" // Keep these string and char prefixes the same!
+# define TASK_VALUE_PRESENTATION_PREFIX_CHAR '$'
+#endif // if FEATURE_STRING_VARIABLES
+
+
// --- We define the default features to be enabled here
#ifndef FEATURE_ESPEASY_P2P
#define FEATURE_ESPEASY_P2P 1
diff --git a/src/src/CustomBuild/StorageLayout.h b/src/src/CustomBuild/StorageLayout.h
index 3afa28391..5b78641a0 100644
--- a/src/src/CustomBuild/StorageLayout.h
+++ b/src/src/CustomBuild/StorageLayout.h
@@ -216,7 +216,7 @@
# define DAT_OFFSET_CDN (DAT_OFFSET_CONTROLLER - DAT_CDN_SIZE) // single CDN settings block of 1k
# endif
# ifndef CONFIG_FILE_SIZE
- # define CONFIG_FILE_SIZE 131072
+ # define CONFIG_FILE_SIZE (DAT_OFFSET_TASKS + ((DAT_TASKS_SIZE + DAT_TASKS_CUSTOM_SIZE) * TASKS_MAX))
# endif // ifndef CONFIG_FILE_SIZE
#endif // if defined(ESP32)
diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h
index 53dfb5898..feada0344 100644
--- a/src/src/CustomBuild/define_plugin_sets.h
+++ b/src/src/CustomBuild/define_plugin_sets.h
@@ -3541,7 +3541,11 @@ To create/register a plugin, you have to :
#endif
#ifndef FEATURE_TARSTREAM_SUPPORT
- #define FEATURE_TARSTREAM_SUPPORT 1
+ #ifdef LIMIT_BUILD_SIZE
+ #define FEATURE_TARSTREAM_SUPPORT 0
+ #else
+ #define FEATURE_TARSTREAM_SUPPORT 1
+ #endif
#endif // FEATURE_TARSTREAM_SUPPORT
// Check for plugins that will use Extended Custom Settings storage when available
@@ -3626,7 +3630,18 @@ To create/register a plugin, you have to :
#endif
#endif
-
+#ifndef FEATURE_STRING_VARIABLES
+ #ifdef ESP32
+ #define FEATURE_STRING_VARIABLES 1
+ #endif
+ #ifdef ESP8266
+ #define FEATURE_STRING_VARIABLES 0
+ #endif
+#endif // ifndef FEATURE_STRING_VARIABLES
+#if FEATURE_STRING_VARIABLES && defined(ESP8266) // NOT supported on ESP8266 because of limited available memory
+ #undef FEATURE_STRING_VARIABLES
+ #define FEATURE_STRING_VARIABLES 0
+#endif
#if !defined(CUSTOM_BUILD_CDN_URL) && !defined(FEATURE_ALTERNATIVE_CDN_URL)
diff --git a/src/src/DataStructs/PluginStats.cpp b/src/src/DataStructs/PluginStats.cpp
index 6d0fb343a..c836af50a 100644
--- a/src/src/DataStructs/PluginStats.cpp
+++ b/src/src/DataStructs/PluginStats.cpp
@@ -578,12 +578,7 @@ void PluginStats::plot_ChartJS_dataset() const
addHtml(',');
}
- if (!isnan((*_samples)[i])) {
- addHtmlFloat((*_samples)[i], _nrDecimals);
- }
- else {
- addHtml(F("null"));
- }
+ addHtmlFloat_NaN_toNull((*_samples)[i], _nrDecimals);
}
add_ChartJS_dataset_footer();
}
diff --git a/src/src/DataStructs/PluginStats_array.cpp b/src/src/DataStructs/PluginStats_array.cpp
index 37141eedf..754a1694a 100644
--- a/src/src/DataStructs/PluginStats_array.cpp
+++ b/src/src/DataStructs/PluginStats_array.cpp
@@ -368,8 +368,8 @@ void PluginStats_array::plot_ChartJS(bool onlyJSON) const
if (_plugin_stats_timestamps != nullptr) {
struct tm ts;
uint32_t unix_time_frac{};
- const uint32_t uinxtime_sec = node_time.systemMicros_to_Unixtime((*_plugin_stats_timestamps)[i], unix_time_frac);
- const uint32_t local_timestamp = time_zone.toLocal(uinxtime_sec);
+ const uint32_t unixtime_sec = node_time.systemMicros_to_Unixtime((*_plugin_stats_timestamps)[i], unix_time_frac);
+ const uint32_t local_timestamp = time_zone.toLocal(unixtime_sec);
breakTime(local_timestamp, ts);
addHtml('"');
addHtml(formatDateTimeString(ts));
diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h
index 9a9c03ba4..697eabff2 100644
--- a/src/src/DataStructs/SettingsStruct.h
+++ b/src/src/DataStructs/SettingsStruct.h
@@ -216,6 +216,15 @@ class SettingsStruct_tmpl
bool CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const;
void CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value);
+ #if FEATURE_STRING_VARIABLES
+ bool ShowDerivedTaskValues(taskIndex_t taskIndex) const;
+ void ShowDerivedTaskValues(taskIndex_t taskIndex, bool value);
+ bool EventAndLogDerivedTaskValues(taskIndex_t taskIndex) const;
+ void EventAndLogDerivedTaskValues(taskIndex_t taskIndex, bool value);
+ bool SendDerivedTaskValues(taskIndex_t taskIndex) const;
+ void SendDerivedTaskValues(taskIndex_t taskIndex, bool value);
+ #endif // if FEATURE_STRING_VARIABLES
+
bool DoNotStartAP() const { return VariousBits_1.DoNotStartAP; }
void DoNotStartAP(bool value) { VariousBits_1.DoNotStartAP = value; }
diff --git a/src/src/DataStructs/UserVarStruct.cpp b/src/src/DataStructs/UserVarStruct.cpp
index 1552a4c6a..ee579afe3 100644
--- a/src/src/DataStructs/UserVarStruct.cpp
+++ b/src/src/DataStructs/UserVarStruct.cpp
@@ -431,7 +431,11 @@ bool UserVarStruct::applyFormula(taskIndex_t taskIndex,
String formula = getPreprocessedFormula(taskIndex, varNr);
bool res = true;
- if (!formula.isEmpty())
+ if (!formula.isEmpty()
+ #if FEATURE_STRING_VARIABLES
+ && formula[1] != TASK_VALUE_PRESENTATION_PREFIX_CHAR
+ #endif // FEATURE_STRING_VARIABLES
+ ) // Ignore display-formula
{
START_TIMER;
@@ -471,7 +475,11 @@ bool UserVarStruct::applyFormulaAndSet(taskIndex_t taskIndex
const ESPEASY_RULES_FLOAT_TYPE& value,
Sensor_VType sensorType)
{
- if (!Cache.hasFormula(taskIndex, varNr)) {
+ if (!Cache.hasFormula(taskIndex, varNr)
+ #if FEATURE_STRING_VARIABLES
+ || Cache.getTaskDeviceFormula(taskIndex, varNr)[1] == TASK_VALUE_PRESENTATION_PREFIX_CHAR
+ #endif // if FEATURE_STRING_VARIABLES
+ ) {
_rawData[taskIndex].set(varNr, value, sensorType);
return true;
}
diff --git a/src/src/DataStructs/UserVarStruct.h b/src/src/DataStructs/UserVarStruct.h
index 2c8568f25..eb0e5d818 100644
--- a/src/src/DataStructs/UserVarStruct.h
+++ b/src/src/DataStructs/UserVarStruct.h
@@ -166,9 +166,11 @@ private:
String getPreprocessedFormula(taskIndex_t taskIndex,
taskVarIndex_t varNr) const;
+public:
String getPreviousValue(taskIndex_t taskIndex,
taskVarIndex_t varNr,
Sensor_VType sensorType) const;
+private:
#ifndef LIMIT_BUILD_SIZE
mutable std::map_preprocessedFormula;
#endif // ifndef LIMIT_BUILD_SIZE
diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp
index 7d0aba71d..539283457 100644
--- a/src/src/DataStructs_templ/SettingsStruct.cpp
+++ b/src/src/DataStructs_templ/SettingsStruct.cpp
@@ -207,6 +207,53 @@ void SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t tas
bitWrite(TaskDeviceSendDataFlags[taskIndex], 0, value);
}
}
+
+#if FEATURE_STRING_VARIABLES
+template
+bool SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex) const {
+ if (validTaskIndex(taskIndex)) {
+ return bitRead(TaskDeviceSendDataFlags[taskIndex], 1);
+ }
+ return false;
+}
+
+template
+void SettingsStruct_tmpl::ShowDerivedTaskValues(taskIndex_t taskIndex, bool value) {
+ if (validTaskIndex(taskIndex)) {
+ bitWrite(TaskDeviceSendDataFlags[taskIndex], 1, value);
+ }
+}
+
+template
+bool SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex) const {
+ if (validTaskIndex(taskIndex)) {
+ return bitRead(TaskDeviceSendDataFlags[taskIndex], 2);
+ }
+ return false;
+}
+
+template
+void SettingsStruct_tmpl::EventAndLogDerivedTaskValues(taskIndex_t taskIndex, bool value) {
+ if (validTaskIndex(taskIndex)) {
+ bitWrite(TaskDeviceSendDataFlags[taskIndex], 2, value);
+ }
+}
+
+template
+bool SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex) const {
+ if (validTaskIndex(taskIndex)) {
+ return bitRead(TaskDeviceSendDataFlags[taskIndex], 3);
+ }
+ return false;
+}
+
+template
+void SettingsStruct_tmpl::SendDerivedTaskValues(taskIndex_t taskIndex, bool value) {
+ if (validTaskIndex(taskIndex)) {
+ bitWrite(TaskDeviceSendDataFlags[taskIndex], 3, value);
+ }
+}
+#endif // if FEATURE_STRING_VARIABLES
/*
template
bool SettingsStruct_tmpl::DoNotStartAP() const {
diff --git a/src/src/ESPEasyCore/ESPEasyRules.cpp b/src/src/ESPEasyCore/ESPEasyRules.cpp
index bf7e4dc0e..1c9832b3d 100644
--- a/src/src/ESPEasyCore/ESPEasyRules.cpp
+++ b/src/src/ESPEasyCore/ESPEasyRules.cpp
@@ -239,7 +239,7 @@ bool rulesProcessingFile(const String& fileName,
/********************************************************************************************\
Parse string commands
\*********************************************************************************************/
-bool get_next_inner_bracket(const String& line, int& startIndex, int& closingIndex, char closingBracket)
+bool get_next_inner_bracket(const String& line, unsigned int& startIndex, int& closingIndex, char closingBracket)
{
if (line.length() <= 1) {
// Not possible to have opening and closing bracket on a line this short.
@@ -263,7 +263,7 @@ bool get_next_inner_bracket(const String& line, int& startIndex, int& closingInd
return false;
}
- for (int i = (closingIndex - 1); i > startIndex; --i) {
+ for (int i = (closingIndex - 1); (i >= static_cast(startIndex)) && (i >= 0); --i) {
if (line[i] == openingBracket) {
startIndex = i;
return true;
@@ -441,7 +441,11 @@ bool parse_math_functions(const String& cmd_s_lower, const String& arg1, const S
return true;
}
-const char string_commands[] PROGMEM = "substring|indexof|indexof_ci|equals|equals_ci|timetomin|timetosec|strtol|tobin|tohex|ord|urlencode";
+const char string_commands[] PROGMEM = "substring|indexof|indexof_ci|equals|equals_ci|timetomin|timetosec|strtol|tobin|tohex|ord|urlencode"
+ #if FEATURE_STRING_VARIABLES
+ "|lookup"
+ #endif // if FEATURE_STRING_VARIABLES
+ ;
enum class string_commands_e {
substring,
indexof,
@@ -454,12 +458,15 @@ enum class string_commands_e {
tobin,
tohex,
ord,
- urlencode
+ urlencode,
+ #if FEATURE_STRING_VARIABLES
+ lookup,
+ #endif // if FEATURE_STRING_VARIABLES
};
void parse_string_commands(String& line) {
- int startIndex = 0;
+ unsigned int startIndex = 0;
int closingIndex;
bool mustReplaceMaskedChars = false;
@@ -534,6 +541,13 @@ void parse_string_commands(String& line) {
}
}
break;
+ #if FEATURE_STRING_VARIABLES
+ case string_commands_e::lookup:
+ if (arg1valid && arg2valid && startpos > -1 && endpos > -1) {
+ replacement = arg3.substring(startpos * endpos, (startpos + 1) * endpos);
+ }
+ break;
+ #endif // if FEATURE_STRING_VARIABLES
case string_commands_e::indexof:
case string_commands_e::indexof_ci:
// indexOf arduino style (0-based position of first char returned, -1 if not found, case sensitive), 3rd argument is search-offset
@@ -1278,12 +1292,26 @@ bool conditionMatch(const String& check) {
}
balanceParentheses(tmpCheck1);
balanceParentheses(tmpCheck2);
- if (isError(Calculate(tmpCheck1, Value1)) ||
- isError(Calculate(tmpCheck2, Value2)))
+ if (isError(Calculate(tmpCheck1, Value1
+ #if FEATURE_STRING_VARIABLES
+ , false // suppress logging specific errors when parsing strings
+ #endif // if FEATURE_STRING_VARIABLES
+ )) ||
+ isError(Calculate(tmpCheck2, Value2
+ #if FEATURE_STRING_VARIABLES
+ , false // suppress logging specific errors when parsing strings
+ #endif // if FEATURE_STRING_VARIABLES
+ )))
{
+ #if FEATURE_STRING_VARIABLES
+ result = compareStringValues(compare, tmpCheck1, tmpCheck2);
+ #else // if FEATURE_STRING_VARIABLES
return false;
+ #endif // if FEATURE_STRING_VARIABLES
+ }
+ else {
+ result = compareDoubleValues(compare, Value1, Value2);
}
- result = compareDoubleValues(compare, Value1, Value2);
}
#ifndef BUILD_NO_DEBUG
@@ -1337,6 +1365,7 @@ void createRuleEvents(struct EventStruct *event) {
if (!validDeviceIndex(DeviceIndex)) { return; }
const uint8_t valueCount = getValueCountForTask(event->TaskIndex);
+ String taskName = getTaskDeviceName(event->TaskIndex);
// Small optimization as sensor type string may result in large strings
// These also only yield a single value, so no need to check for combining task values.
@@ -1354,7 +1383,7 @@ void createRuleEvents(struct EventStruct *event) {
addLog(LOG_LEVEL_ERROR, F("Not enough memory for event"));
return;
}
- eventString += getTaskDeviceName(event->TaskIndex);
+ eventString += taskName;
eventString += '#';
eventString += Cache.getTaskDeviceValueName(event->TaskIndex, 0);
eventString += '=';
@@ -1372,16 +1401,67 @@ void createRuleEvents(struct EventStruct *event) {
String eventvalues;
reserve_special(eventvalues, 32); // Enough for most use cases, prevent lots of memory allocations.
- for (uint8_t varNr = 0; varNr < valueCount; varNr++) {
+ uint8_t varNr = 0;
+ for (; varNr < valueCount; ++varNr) {
if (varNr != 0) {
eventvalues += ',';
}
eventvalues += formatUserVarNoCheck(event, varNr);
}
+ #if FEATURE_STRING_VARIABLES
+ if (Settings.EventAndLogDerivedTaskValues(event->TaskIndex)) {
+ taskName.toLowerCase();
+ String search = strformat(F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), taskName.c_str(), FsP(F("X")));
+ const String postfix = search.substring(search.indexOf('X') + 1);
+ search = search.substring(0, search.indexOf('X')); // Cut off left of valuename
+
+ auto it = customStringVar.begin();
+ while (it != customStringVar.end()) {
+ if (it->first.startsWith(search) && it->first.endsWith(postfix)) {
+ if (!it->second.isEmpty()) {
+ String value(it->second);
+ value = parseTemplateAndCalculate(value);
+ if (varNr != 0) {
+ eventvalues += ',';
+ }
+ eventvalues += value;
+ ++varNr;
+ }
+ }
+ ++it;
+ }
+ }
+ #endif // if FEATURE_STRING_VARIABLES
eventQueue.add(event->TaskIndex, F("All"), eventvalues);
} else {
for (uint8_t varNr = 0; varNr < valueCount; varNr++) {
eventQueue.add(event->TaskIndex, Cache.getTaskDeviceValueName(event->TaskIndex, varNr), formatUserVarNoCheck(event, varNr));
}
+ #if FEATURE_STRING_VARIABLES
+ if (Settings.EventAndLogDerivedTaskValues(event->TaskIndex)) {
+ taskName.toLowerCase();
+ String search = strformat(F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), taskName.c_str(), FsP(F("X")));
+ const String postfix = search.substring(search.indexOf('X') + 1);
+ search = search.substring(0, search.indexOf('X')); // Cut off left of valuename
+
+ auto it = customStringVar.begin();
+ while (it != customStringVar.end()) {
+ if (it->first.startsWith(search) && it->first.endsWith(postfix)) {
+ String valueName = it->first.substring(search.length(), it->first.indexOf('-'));
+ const String key2 = strformat(F(TASK_VALUE_NAME_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str());
+ const String vname2 = getCustomStringVar(key2);
+ if (!vname2.isEmpty()) {
+ valueName = vname2;
+ }
+ if (!it->second.isEmpty()) {
+ String value(it->second);
+ value = parseTemplateAndCalculate(value);
+ eventQueue.add(event->TaskIndex, valueName, value);
+ }
+ }
+ ++it;
+ }
+ }
+ #endif // if FEATURE_STRING_VARIABLES
}
}
diff --git a/src/src/ESPEasyCore/ESPEasyRules.h b/src/src/ESPEasyCore/ESPEasyRules.h
index c51a96dc8..9d2de45ec 100644
--- a/src/src/ESPEasyCore/ESPEasyRules.h
+++ b/src/src/ESPEasyCore/ESPEasyRules.h
@@ -45,7 +45,7 @@ bool rulesProcessingFile(const String& fileName,
\*********************************************************************************************/
bool get_next_inner_bracket(const String& line,
- int & startIndex,
+ unsigned int& startIndex,
int & closingIndex,
char closingBracket);
diff --git a/src/src/Globals/RulesCalculate.cpp b/src/src/Globals/RulesCalculate.cpp
index 6a339417a..9c95d3de5 100644
--- a/src/src/Globals/RulesCalculate.cpp
+++ b/src/src/Globals/RulesCalculate.cpp
@@ -45,8 +45,8 @@ int CalculateParam(const String& TmpStr, int errorValue) {
return returnValue;
}
-CalculateReturnCode Calculate_preProcessed(const String& preprocessd_input,
- ESPEASY_RULES_FLOAT_TYPE & result)
+CalculateReturnCode Calculate_preProcessed(const String & preprocessd_input,
+ ESPEASY_RULES_FLOAT_TYPE& result)
{
START_TIMER;
CalculateReturnCode returnCode = RulesCalculate.doCalculate(
@@ -57,14 +57,22 @@ CalculateReturnCode Calculate_preProcessed(const String& preprocessd_input,
return returnCode;
}
-
CalculateReturnCode Calculate(const String& input,
- ESPEASY_RULES_FLOAT_TYPE & result)
+ ESPEASY_RULES_FLOAT_TYPE& result
+ #if FEATURE_STRING_VARIABLES
+ , const bool logStringErrors
+ #endif // if FEATURE_STRING_VARIABLES
+ )
{
CalculateReturnCode returnCode = Calculate_preProcessed(
RulesCalculate_t::preProces(input),
result);
-#ifndef LIMIT_BUILD_SIZE
+
+ #ifndef LIMIT_BUILD_SIZE
+ # if FEATURE_STRING_VARIABLES
+ bool skipError = false;
+ # endif // if FEATURE_STRING_VARIABLES
+
if (isError(returnCode)) {
if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
String log = F("Calculate: ");
@@ -81,32 +89,43 @@ CalculateReturnCode Calculate(const String& input,
break;
case CalculateReturnCode::ERROR_UNKNOWN_TOKEN:
log += F("Unknown token");
+ # if FEATURE_STRING_VARIABLES
+ skipError = !logStringErrors;
+ # endif // if FEATURE_STRING_VARIABLES
break;
case CalculateReturnCode::ERROR_TOKEN_LENGTH_EXCEEDED:
log += strformat(F("Exceeded token length (%d)"), TOKEN_LENGTH);
+ # if FEATURE_STRING_VARIABLES
+ skipError = !logStringErrors;
+ # endif // if FEATURE_STRING_VARIABLES
break;
case CalculateReturnCode::OK:
// Already handled, but need to have all cases here so the compiler can warn if we're missing one.
break;
}
- #ifndef BUILD_NO_DEBUG
- log += F(" input: ");
- log += input;
- log += F(" = ");
+ # if FEATURE_STRING_VARIABLES
- const bool trimTrailingZeros = true;
-#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
- log += doubleToString(result, 6, trimTrailingZeros);
-#else
- log += floatToString(result, 6, trimTrailingZeros);
-#endif
- #endif // ifndef BUILD_NO_DEBUG
+ if (!skipError)
+ # endif // if FEATURE_STRING_VARIABLES
+ {
+ # ifndef BUILD_NO_DEBUG
+ log += F(" input: ");
+ log += input;
+ log += F(" = ");
- addLogMove(LOG_LEVEL_ERROR, log);
+ const bool trimTrailingZeros = true;
+ # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
+ log += doubleToString(result, 6, trimTrailingZeros);
+ # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
+ log += floatToString(result, 6, trimTrailingZeros);
+ # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
+ # endif // ifndef BUILD_NO_DEBUG
+
+ addLogMove(LOG_LEVEL_ERROR, log);
+ }
}
}
-#endif
+ #endif // ifndef LIMIT_BUILD_SIZE
return returnCode;
}
-
diff --git a/src/src/Globals/RulesCalculate.h b/src/src/Globals/RulesCalculate.h
index b22e2e48a..1de0df501 100644
--- a/src/src/Globals/RulesCalculate.h
+++ b/src/src/Globals/RulesCalculate.h
@@ -17,14 +17,19 @@ extern RulesCalculate_t RulesCalculate;
* Helper functions to actually interact with the rules calculation functions.
* *****************************************************************************************/
-int CalculateParam(const String& TmpStr, int errorValue = 0);
+int CalculateParam(const String& TmpStr,
+ int errorValue = 0);
-CalculateReturnCode Calculate_preProcessed(const String& preprocessd_input,
- ESPEASY_RULES_FLOAT_TYPE & result);
+CalculateReturnCode Calculate_preProcessed(const String & preprocessd_input,
+ ESPEASY_RULES_FLOAT_TYPE& result);
CalculateReturnCode Calculate(const String& input,
- ESPEASY_RULES_FLOAT_TYPE & result);
+ ESPEASY_RULES_FLOAT_TYPE& result
+ #if FEATURE_STRING_VARIABLES
+ ,
+ const bool logStringErrors = true
+ #endif // if FEATURE_STRING_VARIABLES
+ );
-
-#endif
\ No newline at end of file
+#endif // ifndef GLOBALS_RULESCALCULATE_H
diff --git a/src/src/Globals/RuntimeData.cpp b/src/src/Globals/RuntimeData.cpp
index 4a5b326c4..cccd64d32 100644
--- a/src/src/Globals/RuntimeData.cpp
+++ b/src/src/Globals/RuntimeData.cpp
@@ -3,6 +3,9 @@
std::map customFloatVar;
+#if FEATURE_STRING_VARIABLES
+std::map customStringVar;
+#endif // if FEATURE_STRING_VARIABLES
//float UserVar[VARS_PER_TASK * TASKS_MAX];
UserVarStruct UserVar;
@@ -41,3 +44,60 @@ bool getNextCustomFloatVar(String& indexName, ESPEASY_RULES_FLOAT_TYPE& value) {
value = it->second;
return true;
}
+
+#if FEATURE_STRING_VARIABLES
+String getCustomStringVar(String indexName, String defaultValue) {
+ indexName.toLowerCase();
+ auto it = customStringVar.find(indexName);
+
+ if (it != customStringVar.end()) {
+ return it->second;
+ }
+ return defaultValue;
+}
+
+void setCustomStringVar(String indexName, const String& value) {
+ indexName.toLowerCase();
+ // std::map doesn't handle 2nd heap well, so make sure we keep using the default heap.
+ # ifdef USE_SECOND_HEAP
+ HeapSelectDram ephemeral;
+ # endif // ifdef USE_SECOND_HEAP
+
+ customStringVar[indexName] = value;
+}
+
+void clearCustomStringVar(String indexName) {
+ indexName.toLowerCase();
+ // std::map doesn't handle 2nd heap well, so make sure we keep using the default heap.
+ # ifdef USE_SECOND_HEAP
+ HeapSelectDram ephemeral;
+ # endif // ifdef USE_SECOND_HEAP
+
+ auto it = customStringVar.find(indexName);
+
+ if (it != customStringVar.end()) {
+ customStringVar.erase(it);
+ }
+}
+
+bool hasCustomStringVar(String indexName) {
+ indexName.toLowerCase();
+ auto it = customStringVar.find(indexName);
+
+ return it != customStringVar.end();
+}
+
+bool getNextCustomStringVar(String& indexName, String& value) {
+ String valueName(indexName);
+ valueName.toLowerCase();
+ auto it = customStringVar.find(valueName);
+
+ if (it == customStringVar.end()) { return false; }
+ ++it;
+
+ if (it == customStringVar.end()) { return false; }
+ indexName = it->first;
+ value = it->second;
+ return true;
+}
+#endif // if FEATURE_STRING_VARIABLES
diff --git a/src/src/Globals/RuntimeData.h b/src/src/Globals/RuntimeData.h
index 0727d0c73..990d50e82 100644
--- a/src/src/Globals/RuntimeData.h
+++ b/src/src/Globals/RuntimeData.h
@@ -20,12 +20,23 @@
* if %v_two%=20 do ...
\*********************************************************************************************/
extern std::map customFloatVar;
+#if FEATURE_STRING_VARIABLES
+extern std::map customStringVar;
+#endif // if FEATURE_STRING_VARIABLES
ESPEASY_RULES_FLOAT_TYPE getCustomFloatVar(String index, ESPEASY_RULES_FLOAT_TYPE defaultValue = 0.0);
void setCustomFloatVar(String index, const ESPEASY_RULES_FLOAT_TYPE& value);
bool getNextCustomFloatVar(String& index, ESPEASY_RULES_FLOAT_TYPE& value);
+#if FEATURE_STRING_VARIABLES
+String getCustomStringVar(String index, String defaultValue = EMPTY_STRING);
+void setCustomStringVar(String index, const String& value);
+void clearCustomStringVar(String index);
+bool hasCustomStringVar(String index);
+
+bool getNextCustomStringVar(String& index, String& value);
+#endif // if FEATURE_STRING_VARIABLES
/*********************************************************************************************\
* Task Value data.
diff --git a/src/src/Helpers/ESPEasy_time_calc.cpp b/src/src/Helpers/ESPEasy_time_calc.cpp
index 1c6be17a9..84a8b093b 100644
--- a/src/src/Helpers/ESPEasy_time_calc.cpp
+++ b/src/src/Helpers/ESPEasy_time_calc.cpp
@@ -142,10 +142,24 @@ void breakTime(unsigned long timeInput, struct tm& tm) {
int year = 1970;
unsigned long days = 0;
+ #ifndef LIMIT_BUILD_SIZE
+ if (time > 20089) {
+ // Skip checking 55 years for being leap year.
+ year = 2025;
+ days = 20089;
+ }
+ #endif
while ((unsigned)(days += (isLeapYear(year) ? 366 : 365)) <= time) {
year++;
}
+/*
+ addLog(LOG_LEVEL_INFO, strformat(
+ F("breakTime: time: %u year: %d, days: %u"),
+ time,
+ year,
+ days));
+*/
tm.tm_year = year - 1900; // tm_year starts at 1900
days -= isLeapYear(year) ? 366 : 365;
diff --git a/src/src/Helpers/Misc.cpp b/src/src/Helpers/Misc.cpp
index e1aa341ce..4ac69c60b 100644
--- a/src/src/Helpers/Misc.cpp
+++ b/src/src/Helpers/Misc.cpp
@@ -146,7 +146,7 @@ void taskClear(taskIndex_t taskIndex, bool save)
if (save) {
#ifndef BUILD_MINIMAL_OTA
addLog(LOG_LEVEL_INFO, F("taskClear() save settings"));
- #endif
+ #endif // ifndef BUILD_MINIMAL_OTA
SaveTaskSettings(taskIndex);
SaveSettings();
}
@@ -314,13 +314,14 @@ void SendValueLogger(taskIndex_t TaskIndex)
if (validDeviceIndex(DeviceIndex)) {
const uint8_t valueCount = getValueCountForTask(TaskIndex);
+ String taskName = getTaskDeviceName(TaskIndex);
const String logline_prefix =
strformat(F("%s %s,%d,%s")
, node_time.getDateString('-').c_str()
, node_time.getTimeString(':').c_str()
, Settings.Unit
- , getTaskDeviceName(TaskIndex).c_str()
+ , taskName.c_str()
);
for (uint8_t varNr = 0; varNr < valueCount; varNr++)
@@ -331,6 +332,40 @@ void SendValueLogger(taskIndex_t TaskIndex)
, formatUserVarNoCheck(TaskIndex, varNr).c_str()
);
}
+ # if FEATURE_STRING_VARIABLES
+
+ if (Settings.EventAndLogDerivedTaskValues(TaskIndex)) {
+ taskName.toLowerCase();
+ String search = strformat(F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), taskName.c_str(), FsP(F("X")));
+ const String postfix = search.substring(search.indexOf('X') + 1);
+ search = search.substring(0, search.indexOf('X')); // Cut off left of valuename
+
+ auto it = customStringVar.begin();
+
+ while (it != customStringVar.end()) {
+ if (it->first.startsWith(search) && it->first.endsWith(postfix)) {
+ String valueName = it->first.substring(search.length(), it->first.indexOf('-'));
+ const String key2 = strformat(F(TASK_VALUE_NAME_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str());
+ const String vname2 = getCustomStringVar(key2);
+
+ if (!vname2.isEmpty()) {
+ valueName = vname2;
+ }
+
+ if (!it->second.isEmpty()) {
+ String value(it->second);
+ value = parseTemplateAndCalculate(value);
+ logger += strformat(F("%s,%s,%s\r\n")
+ , logline_prefix.c_str()
+ , valueName.c_str()
+ , value.c_str()
+ );
+ }
+ }
+ ++it;
+ }
+ }
+ # endif // if FEATURE_STRING_VARIABLES
# ifndef BUILD_NO_DEBUG
addLog(LOG_LEVEL_DEBUG, logger);
# endif // ifndef BUILD_NO_DEBUG
diff --git a/src/src/Helpers/RulesMatcher.cpp b/src/src/Helpers/RulesMatcher.cpp
index 0093d45b1..a81c02e1b 100644
--- a/src/src/Helpers/RulesMatcher.cpp
+++ b/src/src/Helpers/RulesMatcher.cpp
@@ -133,6 +133,23 @@ bool compareIntValues(char compare,
return false;
}
+#if FEATURE_STRING_VARIABLES
+bool compareStringValues(char compare,
+ String& Value1,
+ String& Value2)
+{
+ switch (compare) {
+ case '>' + '=': return Value1 >= Value2;
+ case '<' + '=': return Value1 <= Value2;
+ case '<' + '>': return !Value1.equals(Value2);
+ case '>': return Value1 > Value2;
+ case '<': return Value1 < Value2;
+ case '=': return Value1.equals(Value2);
+ }
+ return false;
+}
+#endif // if FEATURE_STRING_VARIABLES
+
bool compareDoubleValues(char compare,
const ESPEASY_RULES_FLOAT_TYPE& Value1,
const ESPEASY_RULES_FLOAT_TYPE& Value2,
diff --git a/src/src/Helpers/RulesMatcher.h b/src/src/Helpers/RulesMatcher.h
index 7261c43de..83826d037 100644
--- a/src/src/Helpers/RulesMatcher.h
+++ b/src/src/Helpers/RulesMatcher.h
@@ -15,6 +15,12 @@ bool compareIntValues(char compare,
int64_t Value1,
int64_t Value2);
+#if FEATURE_STRING_VARIABLES
+bool compareStringValues(char compare,
+ String& Value1,
+ String& Value2);
+#endif // if FEATURE_STRING_VARIABLES
+
bool compareDoubleValues(char compare,
const ESPEASY_RULES_FLOAT_TYPE& Value1,
const ESPEASY_RULES_FLOAT_TYPE& Value2,
diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp
index 19fc5e317..10e19ffb4 100644
--- a/src/src/Helpers/StringConverter.cpp
+++ b/src/src/Helpers/StringConverter.cpp
@@ -541,6 +541,49 @@ String formatUserVar(struct EventStruct *event, uint8_t rel_index, bool& isvalid
return doFormatUserVar(event, rel_index, true, isvalid);
}
+#if FEATURE_STRING_VARIABLES
+String formatUserVarForPresentation(struct EventStruct *event,
+ taskVarIndex_t varNr,
+ bool & hasPresentation,
+ const String & value,
+ const deviceIndex_t DeviceIndex,
+ String valueName) {
+ const taskIndex_t taskIndex = event->TaskIndex;
+ String formula = Cache.getTaskDeviceFormula(taskIndex, varNr);
+ hasPresentation = Cache.hasFormula(taskIndex, varNr)
+ && formula.startsWith(F(TASK_VALUE_PRESENTATION_PREFIX_STRING));
+ if (hasPresentation) {
+ formula.remove(0, 1);
+ formula.replace(F("%value%"), value);
+ if (Cache.hasFormula_with_prevValue(taskIndex, varNr)) {
+ event->sensorType = Device[DeviceIndex].VType;
+ String dummy;
+ PluginCall(PLUGIN_GET_DEVICEVTYPE, event, dummy);
+ const String prev_str = UserVar.getPreviousValue(taskIndex, varNr, event->sensorType);
+ formula.replace(F("%pvalue%"), prev_str.isEmpty() ? value : prev_str);
+ }
+ formula = parseTemplate(formula);
+ return formula;
+ } else {
+ String taskName = getTaskDeviceName(taskIndex);
+ taskName.toLowerCase();
+ if (valueName.isEmpty() && validTaskVarIndex(varNr)) {
+ valueName = getTaskValueName(taskIndex, varNr);
+ }
+ valueName.toLowerCase();
+ String presentation = getCustomStringVar(strformat(F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str()));
+ if (!presentation.isEmpty()) {
+ stripEscapeCharacters(presentation);
+ presentation.replace(F("%value%"), value);
+ presentation = parseTemplate(presentation);
+ }
+ hasPresentation = !presentation.isEmpty();
+ return presentation;
+ }
+ return EMPTY_STRING;
+}
+#endif // if FEATURE_STRING_VARIABLES
+
String get_formatted_Controller_number(cpluginID_t cpluginID) {
if (!validCPluginID(cpluginID)) {
return F("C---");
@@ -682,10 +725,12 @@ String to_json_value(const String& value, bool wrapInQuotes) {
// Empty string
return F("\"\"");
}
- if (value.length() > 2) {
+ const size_t val_length = value.length();
+
+ if (val_length > 2) {
// Check for JSON objects or arrays
const char firstchar = value[0];
- const char lastchar = value[value.length() - 1];
+ const char lastchar = value[val_length - 1];
if ((firstchar == '[' && lastchar == ']') ||
(firstchar == '{' && lastchar == '}'))
{
@@ -698,26 +743,34 @@ String to_json_value(const String& value, bool wrapInQuotes) {
// Is not a numerical value, or BIN/HEX notation, thus wrap with quotes
// First we check for not allowed special characters.
- const size_t val_length = value.length();
+
+ // Most frequently found in a string
+ const bool backslash_or_doubleQuote_found =
+ value.indexOf('\\') != -1 ||
+ value.indexOf('"') != -1;
+
for (size_t i = 0; i < val_length; ++i) {
const char c = value[i];
// Special characters not allowed in JSON:
- if (c == '\n'|| // \n New line
- c == '\r'|| // \r Carriage return
- c == '\t'|| // \t Tab
- c == '\\'|| // \\ Backslash character
+ if (backslash_or_doubleQuote_found ||
+ (c >= '\b' && c <= '\r')
+/*
c == '\b'|| // \b Backspace (ascii code 08)
+ c == '\t'|| // \t Tab
+ c == '\n'|| // \n New line
c == '\f'|| // \f Form feed (ascii code 0C)
- c == '"') { // \" Double quote
+ c == '\r'|| // \r Carriage return
+*/
+ ) {
// Must replace characters, so make a deepcopy
String tmpValue(value);
- tmpValue.replace('\n', '^');
- tmpValue.replace('\r', '^');
- tmpValue.replace('\t', ' ');
- tmpValue.replace('\\', '^');
- tmpValue.replace('\b', '^');
- tmpValue.replace('\f', '^');
- tmpValue.replace('"', '\'');
+ tmpValue.replace('\b', '^'); // \b Backspace (ascii code 08)
+ tmpValue.replace('\t', ' '); // \t Tab
+ tmpValue.replace('\n', '^'); // \n New line
+ tmpValue.replace('\f', '^'); // \f Form feed (ascii code 0C)
+ tmpValue.replace('\r', '^'); // \r Carriage return
+ tmpValue.replace('\\', '^'); // Backslash
+ tmpValue.replace('"', '\''); // Double quote
return wrap_String(tmpValue, '"');
}
}
@@ -1393,7 +1446,9 @@ bool getConvertArgument2(const __FlashStringHelper * marker, const String& s, fl
if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) {
const int pos_comma = argumentString.indexOf(',');
- if (pos_comma == -1) { return false; }
+ if (pos_comma == -1) {
+ return validFloatFromString(argumentString, arg1); // Accept single argument, with second argument 0
+ }
if (validFloatFromString(argumentString.substring(0, pos_comma), arg1)) {
return validFloatFromString(argumentString.substring(pos_comma + 1), arg2);
@@ -1402,6 +1457,53 @@ bool getConvertArgument2(const __FlashStringHelper * marker, const String& s, fl
return false;
}
+#if FEATURE_STRING_VARIABLES
+bool getConvertArgumentStr(const __FlashStringHelper * marker, const String& s, String& argument, int& startIndex, int& endIndex) {
+ return getConvertArgumentString(marker, s, argument, startIndex, endIndex);
+}
+
+#define _MAX_STRFORMAT_ARGUMENTS 2 // Max. 2 numeric arguments for now to avoid strformat() from crashing when reading the 3rd argument that's not there
+bool getConvertArgumentStrFormat(const __FlashStringHelper * marker, const String& s, String& argStr, float& arg1, float& arg2, int& startIndex, int& endIndex) {
+ String argumentString;
+
+ if (getConvertArgumentString(marker, s, argumentString, startIndex, endIndex)) {
+ const int pos_comma = argumentString.indexOf(',');
+
+ if (pos_comma == -1) { return false; } // At least 2 arguments required
+
+ argStr = getCustomStringVar(argumentString.substring(0, pos_comma)); // Get String variable content
+
+ if (argStr.isEmpty()) { // Not found or invalid variable name: probably provided a formatstring (not quoted, no commas!)
+ argStr = argumentString.substring(0, pos_comma);
+ }
+
+ int pos_dollar = argStr.indexOf('$');
+ int dollarCount{};
+ while (pos_dollar != -1) {
+ ++dollarCount;
+ ++pos_dollar;
+ pos_dollar = argStr.indexOf('$', pos_dollar);
+ }
+
+ // We support, 0, 1 or 2, $ formatting characters to avoid crashes and confusion with shorthand variables
+ if (dollarCount > _MAX_STRFORMAT_ARGUMENTS) { return false; }
+
+ argStr.replace('$', '%'); // Change to regular strformat() format specifiers
+
+ const int pos_comma2 = argumentString.indexOf(',', pos_comma + 1);
+
+ if (pos_comma2 == -1) {
+ return validFloatFromString(argumentString.substring(pos_comma + 1), arg1);
+ }
+
+ return validFloatFromString(argumentString.substring(pos_comma + 1, pos_comma2), arg1)
+ && validFloatFromString(argumentString.substring(pos_comma2 + 1), arg2);
+ }
+ return false;
+}
+#undef _MAX_STRFORMAT_ARGUMENTS
+#endif // if FEATURE_STRING_VARIABLES
+
bool getConvertArgumentString(const __FlashStringHelper * marker, const String& s, String& argumentString, int& startIndex, int& endIndex) {
return getConvertArgumentString(String(marker), s, argumentString, startIndex, endIndex);
}
@@ -1444,6 +1546,7 @@ struct ConvertArgumentData {
ConvertArgumentData() = delete;
String& str;
+ String str1;
float arg1, arg2;
int startIndex;
int endIndex;
@@ -1462,6 +1565,16 @@ bool getConvertArgument2(const __FlashStringHelper * marker, ConvertArgumentData
return getConvertArgument2(marker, data.str, data.arg1, data.arg2, data.startIndex, data.endIndex);
}
+bool getConvertArgumentStr(const __FlashStringHelper * marker, ConvertArgumentData& data) {
+ return getConvertArgumentStr(marker, data.str, data.str1, data.startIndex, data.endIndex);
+}
+
+#if FEATURE_STRING_VARIABLES
+bool getConvertArgumentStrFormat(const __FlashStringHelper * marker, ConvertArgumentData& data) {
+ return getConvertArgumentStrFormat(marker, data.str, data.str1, data.arg1, data.arg2, data.startIndex, data.endIndex);
+}
+#endif // if FEATURE_STRING_VARIABLES
+
// Parse conversions marked with "%conv_marker%(float)"
// Must be called last, since all sensor values must be converted, processed, etc.
void parseStandardConversions(String& s, bool useURLencode) {
@@ -1486,7 +1599,6 @@ void parseStandardConversions(String& s, bool useURLencode) {
SMART_CONV(F("%c_m2dhm%"), minutesToDayHourMinute(data.arg1))
SMART_CONV(F("%c_m2hcm%"), minutesToHourColonMinute(data.arg1))
SMART_CONV(F("%c_s2dhms%"), secondsToDayHourMinuteSecond(data.arg1))
- SMART_CONV(F("%c_2hex%"), formatToHex_no_prefix(data.arg1))
#if FEATURE_ESPEASY_P2P
SMART_CONV(F("%c_uname%"), getNameForUnit(data.arg1))
SMART_CONV(F("%c_uage%"), String(static_cast(getAgeForUnit(data.arg1) / 1000)))
@@ -1496,17 +1608,24 @@ void parseStandardConversions(String& s, bool useURLencode) {
SMART_CONV(F("%c_utype%"), String(getTypeForUnit(data.arg1)))
SMART_CONV(F("%c_utypestr%"), getTypeStringForUnit(data.arg1))
#endif // if FEATURE_ESPEASY_P2P
+ #if FEATURE_STRING_VARIABLES
+ SMART_CONV(F("%c_ts2wday%"), get_weekday_from_timestamp(static_cast(data.arg1)))
+ #endif // if FEATURE_STRING_VARIABLES
#undef SMART_CONV
// Conversions with 2 parameters
#define SMART_CONV(T, FUN) \
while (getConvertArgument2((T), data)) { repl(data, (FUN)); }
SMART_CONV(F("%c_dew_th%"), toString(compute_dew_point_temp(data.arg1, data.arg2), 2))
+ SMART_CONV(F("%c_2hex%"), formatToHex_no_prefix(data.arg1, data.arg2))
#if FEATURE_ESPEASY_P2P
SMART_CONV(F("%c_u2ip%"), formatUnitToIPAddress(data.arg1, data.arg2))
#endif
SMART_CONV(F("%c_alt_pres_sea%"), toString(altitudeFromPressure(data.arg1, data.arg2), 2))
SMART_CONV(F("%c_sea_pres_alt%"), toString(pressureElevation(data.arg1, data.arg2), 2))
+ #if FEATURE_STRING_VARIABLES
+ SMART_CONV(F("%c_ts2date%"), get_date_time_from_timestamp(static_cast(data.arg1), !essentiallyZero(data.arg2)))
+ #endif // if FEATURE_STRING_VARIABLES
#ifndef LIMIT_BUILD_SIZE
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
@@ -1516,8 +1635,37 @@ void parseStandardConversions(String& s, bool useURLencode) {
#endif
#endif
#undef SMART_CONV
+
+ #if FEATURE_STRING_VARIABLES
+ double tmp{};
+ #define SMART_CONV(T, FUN) \
+ while (getConvertArgumentStr((T), data)) { repl(data, (FUN)); }
+ SMART_CONV(F("%c_isnum%"), String(validDoubleFromString(getCustomStringVar(data.str1), tmp) ? 1 : 0))
+ #undef SMART_CONV
+ // 1 string and 1 or 2 numeric arguments
+ #define SMART_CONV(T, FUN) \
+ while (getConvertArgumentStrFormat((T), data)) { repl(data, (FUN)); }
+ SMART_CONV(F("%c_strf%"), strformat(data.str1, data.arg1, data.arg2))
+ #undef SMART_CONV
+ #endif // if FEATURE_STRING_VARIABLES
}
+#if FEATURE_STRING_VARIABLES
+String get_date_time_from_timestamp(time_t unix_timestamp, bool am_pm) {
+ struct tm ts;
+ ts = *localtime(&unix_timestamp);
+
+ return formatDateTimeString(ts, '-', ':', ' ', am_pm);
+}
+
+String get_weekday_from_timestamp(time_t unix_timestamp) {
+ struct tm ts;
+ ts = *localtime(&unix_timestamp);
+
+ return String(ts.tm_wday);
+}
+#endif // if FEATURE_STRING_VARIABLES
+
/********************************************************************************************\
Find positional parameter in a char string
\*********************************************************************************************/
diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h
index a8766e1f5..f84a7cc6f 100644
--- a/src/src/Helpers/StringConverter.h
+++ b/src/src/Helpers/StringConverter.h
@@ -184,6 +184,14 @@ String formatUserVar(struct EventStruct *event,
uint8_t rel_index,
bool & isvalid);
+#if FEATURE_STRING_VARIABLES
+String formatUserVarForPresentation(struct EventStruct *event,
+ taskVarIndex_t varNr,
+ bool & hasPresentation,
+ const String & value,
+ const deviceIndex_t DeviceIndex,
+ String valueName = EMPTY_STRING);
+#endif // if FEATURE_STRING_VARIABLES
String get_formatted_Controller_number(cpluginID_t cpluginID);
@@ -422,6 +430,22 @@ bool getConvertArgument2(const __FlashStringHelper * marker,
int & startIndex,
int & endIndex);
+#if FEATURE_STRING_VARIABLES
+bool getConvertArgumentStrFormat(const __FlashStringHelper *marker,
+ const String & s,
+ String & argStr,
+ float & arg1,
+ float & arg2,
+ int & startIndex,
+ int & endIndex);
+#endif // if FEATURE_STRING_VARIABLES
+
+bool getConvertArgumentStr(const __FlashStringHelper *marker,
+ const String & s,
+ String & argument,
+ int & startIndex,
+ int & endIndex);
+
bool getConvertArgumentString(const __FlashStringHelper * marker,
const String& s,
String & argumentString,
@@ -439,6 +463,10 @@ bool getConvertArgumentString(const String& marker,
void parseStandardConversions(String& s,
bool useURLencode);
+#if FEATURE_STRING_VARIABLES
+String get_date_time_from_timestamp(time_t unix_timestamp, bool am_pm);
+String get_weekday_from_timestamp(time_t unix_timestamp);
+#endif // if FEATURE_STRING_VARIABLES
bool HasArgv(const char *string,
unsigned int argc);
diff --git a/src/src/Helpers/StringConverter_Numerical.cpp b/src/src/Helpers/StringConverter_Numerical.cpp
index 4b7847bc6..18e29ad7b 100644
--- a/src/src/Helpers/StringConverter_Numerical.cpp
+++ b/src/src/Helpers/StringConverter_Numerical.cpp
@@ -26,25 +26,40 @@ unsigned long str2int(const char *string)
\*********************************************************************************************/
String toString(const float& value, unsigned int decimalPlaces, bool trimTrailingZeros)
{
-#ifdef ESP32
+ String res;
+ toValidString(res, value, decimalPlaces, trimTrailingZeros);
+ return res;
+}
+
+bool toValidString(String& str,
+ const float& value,
+ unsigned int decimalPlaces,
+ bool trimTrailingZeros)
+{
+ #ifdef ESP32
if (isnanf(value)) {
- return F("NaN");
+ str = F("NaN");
+ return false;
}
if (isinff(value)) {
- return F("Inf");
+ str = F("Inf");
+ return false;
}
#else
if (isnan(value)) {
- return F("NaN");
+ str = F("NaN");
+ return false;
}
if (isinf(value)) {
- return F("Inf");
+ str = F("Inf");
+ return false;
}
#endif
const double value_d(value);
- return doubleToString(value_d, decimalPlaces, trimTrailingZeros);
+ return doubleToValidString(str, value_d, decimalPlaces, trimTrailingZeros);
}
+
String ull2String(uint64_t value, uint8_t base) {
String res;
@@ -117,13 +132,25 @@ String toStringNoZero(int64_t value) {
}
String doubleToString(const double& value, unsigned int decimalPlaces, bool trimTrailingZeros_b) {
+ String res;
+ doubleToValidString(res, value, decimalPlaces, trimTrailingZeros_b);
+ return res;
+}
+
+
+bool doubleToValidString(String& str,
+ const double& value,
+ unsigned int decimalPlaces ,
+ bool trimTrailingZeros_b)
+{
if (isnan(value)) {
- return F("NaN");
+ str = F("NaN");
+ return false;
}
if (isinf(value)) {
- return F("Inf");
+ str = F("Inf");
+ return false;
}
- String res;
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
// We use some trick here to prevent rounding errors
@@ -150,9 +177,10 @@ String doubleToString(const double& value, unsigned int decimalPlaces, bool trim
char *buf = (char *)malloc(expectedChars);
if (nullptr == buf) {
- return F("NaN");
+ str = F("NaN");
+ return false;
}
- move_special(res, String(dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf)));
+ move_special(str, String(dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf)));
free(buf);
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
@@ -182,7 +210,7 @@ String doubleToString(const double& value, unsigned int decimalPlaces, bool trim
// Those cannot be used in the string format, so use it as a preformatted string
const String tmp_str_before_dot = ull2String(int_value / factor);
if (decimalPlaces == 0) {
- res = tmp_str_before_dot;
+ str = tmp_str_before_dot;
} else {
String tmp_str_after_dot;
tmp_str_after_dot.reserve(decimalPlaces);
@@ -192,23 +220,23 @@ String doubleToString(const double& value, unsigned int decimalPlaces, bool trim
tmp_str_after_dot = concat('0', tmp_str_after_dot);
}
- res = strformat(
+ str = strformat(
F("%s.%s"),
tmp_str_before_dot.c_str(),
tmp_str_after_dot.c_str());
}
if (value < 0) {
- res = concat('-', res);
+ str = concat('-', str);
}
}
#endif
- res.trim();
+ str.trim();
if (trimTrailingZeros_b) {
- return trimTrailingZeros(res);
+ str = trimTrailingZeros(str);
}
- return res;
+ return true;
}
String floatToString(const float& value,
diff --git a/src/src/Helpers/StringConverter_Numerical.h b/src/src/Helpers/StringConverter_Numerical.h
index 769f6b66d..ec44ba4db 100644
--- a/src/src/Helpers/StringConverter_Numerical.h
+++ b/src/src/Helpers/StringConverter_Numerical.h
@@ -23,6 +23,12 @@ String toString(const float& value,
unsigned int decimalPlaces = 2,
bool trimTrailingZeros = false);
+bool toValidString(String& str,
+ const float& value,
+ unsigned int decimalPlaces = 2,
+ bool trimTrailingZeros = false);
+
+
String ull2String(uint64_t value,
uint8_t base = 10);
@@ -37,6 +43,11 @@ String doubleToString(const double& value,
unsigned int decimalPlaces = 2,
bool trimTrailingZeros = false);
+bool doubleToValidString(String& str,
+ const double& value,
+ unsigned int decimalPlaces = 2,
+ bool trimTrailingZeros = false);
+
String floatToString(const float& value,
unsigned int decimalPlaces = 2,
bool trimTrailingZeros = false);
diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp
index 755c58fd5..250ff5273 100644
--- a/src/src/Helpers/StringParser.cpp
+++ b/src/src/Helpers/StringParser.cpp
@@ -43,6 +43,22 @@ void stripEscapeCharacters(String& str)
}
}
+#if FEATURE_STRING_VARIABLES
+String parseTemplateAndCalculate(String& tmpString) {
+ stripEscapeCharacters(tmpString);
+ String str = parseTemplate(tmpString);
+ ESPEASY_RULES_FLOAT_TYPE result{};
+ if (!str.isEmpty() && (str[0] == '=') && !isError(Calculate(str.substring(1), result, true))) {
+ # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
+ str = doubleToString(result, 6, true);
+ # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
+ str = floatToString(result, 6, true);
+ # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
+ }
+ return str;
+}
+#endif // if FEATURE_STRING_VARIABLES
+
String parseTemplate(String& tmpString)
{
return parseTemplate(tmpString, false);
@@ -100,13 +116,42 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us
// deviceName is lower case, so we can compare literal string (no need for equalsIgnoreCase)
const bool devNameEqInt = equals(deviceName, F("int"));
- if (devNameEqInt || equals(deviceName, F("var")))
+ #if FEATURE_STRING_VARIABLES
+ const bool devNameEqStr = equals(deviceName, F("str"));
+ const bool devNameEqLength = equals(deviceName, F("length"));
+ #endif // if FEATURE_STRING_VARIABLES
+ if (devNameEqInt || equals(deviceName, F("var"))
+ #if FEATURE_STRING_VARIABLES
+ || devNameEqStr || devNameEqLength
+ #endif // if FEATURE_STRING_VARIABLES
+ )
{
// Address an internal variable either as float or as int
// For example: Let,10,[VAR#9]
// For example: Let,10,[INT#bla]
if (!valueName.isEmpty()) {
+ #if FEATURE_STRING_VARIABLES
+ if (devNameEqStr) {
+ String value(getCustomStringVar(valueName));
+ transformValue(
+ newString,
+ minimal_lineSize,
+ std::move(value),
+ format,
+ tmpString);
+ } else
+ if (devNameEqLength) {
+ String value(getCustomStringVar(valueName).length());
+ transformValue(
+ newString,
+ minimal_lineSize,
+ std::move(value),
+ format,
+ tmpString);
+ } else
+ #endif
+ {
const ESPEASY_RULES_FLOAT_TYPE floatvalue = getCustomFloatVar(valueName);
unsigned char nr_decimals = maxNrDecimals_fpType(floatvalue);
bool trimTrailingZeros = true;
@@ -129,6 +174,7 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us
std::move(value),
format,
tmpString);
+ }
}
}
else if (equals(deviceName, F("plugin")))
@@ -171,7 +217,11 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us
String value = formatUserVar(taskIndex, valueNr, isvalid);
if (isvalid) {
- transformValue(newString, minimal_lineSize, std::move(value), format, tmpString);
+ transformValue(newString, minimal_lineSize, std::move(value), format, tmpString
+ #if FEATURE_STRING_VARIABLES
+ , taskIndex, valueNr, valueName // for handling $ format option
+ #endif // if FEATURE_STRING_VARIABLES
+ );
isHandled = true;
}
} else {
@@ -181,7 +231,11 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us
if (PluginCall(PLUGIN_GET_CONFIG_VALUE, &TempEvent, tmpName))
{
- transformValue(newString, minimal_lineSize, std::move(tmpName), format, tmpString);
+ transformValue(newString, minimal_lineSize, std::move(tmpName), format, tmpString
+ #if FEATURE_STRING_VARIABLES
+ , taskIndex, INVALID_TASKVAR_INDEX, valueName // for handling $ format option
+ #endif // if FEATURE_STRING_VARIABLES
+ );
isHandled = true;
}
}
@@ -212,10 +266,48 @@ String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool us
}
}
if (!value.isEmpty()) {
- transformValue(newString, minimal_lineSize, std::move(value), format, tmpString);
+ transformValue(newString, minimal_lineSize, std::move(value), format, tmpString
+ #if FEATURE_STRING_VARIABLES
+ , taskIndex, INVALID_TASKVAR_INDEX, valueName // for handling $ format option
+ #endif // if FEATURE_STRING_VARIABLES
+ );
// isHandled = true;
}
}
+ #if FEATURE_STRING_VARIABLES
+ if (!isHandled) {
+ String value;
+ const String valName = parseString(valueName, 1);
+ String derived = getCustomStringVar(strformat(F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), deviceName.c_str(), valName.c_str()));
+ if (!derived.isEmpty()) {
+ value = parseTemplateAndCalculate(derived);
+ if (!value.isEmpty()) {
+ transformValue(newString, minimal_lineSize, std::move(value), format, tmpString,
+ taskIndex, INVALID_TASKVAR_INDEX, valName // for handling $ format option
+ );
+ isHandled = true;
+ }
+ }
+ }
+
+ // TODO: Insert TaskValue attribute support from PR #5328 here
+
+ if (!isHandled && valueName.indexOf('.') > -1) {
+ String value;
+ const String fullValueName = parseString(valueName, 1);
+ const String valName = parseString(fullValueName, 1, '.');
+ const String command = parseString(fullValueName, 2, '.');
+ if (equals(command, F("uom"))) { // Fetch UnitOfMeasure
+ value = getCustomStringVar(strformat(F(TASK_VALUE_UOM_PREFIX_TEMPLATE), deviceName.c_str(), valName.c_str()));
+ }
+ if (!value.isEmpty()) {
+ transformValue(newString, minimal_lineSize, std::move(value), format, tmpString,
+ taskIndex, INVALID_TASKVAR_INDEX, valName
+ );
+ // isHandled = true;
+ }
+ }
+ #endif // if FEATURE_STRING_VARIABLES
}
}
@@ -338,10 +430,16 @@ bool isTransformString(char c, bool logicVal, String& strValue)
// valueFormat="transformation#justification"
void transformValue(
String & newString,
- uint8_t lineSize,
+ uint8_t lineSize,
String value,
String & valueFormat,
- const String& tmpString)
+ const String& tmpString
+ #if FEATURE_STRING_VARIABLES
+ , taskIndex_t taskIndex
+ , uint8_t valueIndex
+ , String valueName
+ #endif // if FEATURE_STRING_VARIABLES
+ )
{
// FIXME TD-er: This function does append to newString and uses its length to perform right aling.
// Is this the way it is intended to use?
@@ -495,6 +593,21 @@ void transformValue(
value = static_cast(ceilf(valFloat));
#endif
break;
+ #if FEATURE_STRING_VARIABLES
+ case TASK_VALUE_PRESENTATION_PREFIX_CHAR: // '$' Apply presentation format
+ {
+ if (validTaskIndex(taskIndex) && (validTaskVarIndex(valueIndex) || !valueName.isEmpty())) {
+ const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex);
+ bool hasPresentation = false;
+ EventStruct TempEvent(taskIndex);
+ const String presentation = formatUserVarForPresentation(&TempEvent, valueIndex, hasPresentation, value, DeviceIndex, valueName);
+ if (hasPresentation) {
+ value = presentation;
+ }
+ }
+ break;
+ }
+ #endif // if FEATURE_STRING_VARIABLES
default:
value = F("ERR");
break;
diff --git a/src/src/Helpers/StringParser.h b/src/src/Helpers/StringParser.h
index 5f892dd68..6b29d9878 100644
--- a/src/src/Helpers/StringParser.h
+++ b/src/src/Helpers/StringParser.h
@@ -15,6 +15,10 @@ bool hasEscapedCharacter(String& str, const char EscapeChar);
// So far \\% \\[ \\] \\{ \\} \\( and \\) are used (all with single backslash!)
void stripEscapeCharacters(String& str);
+#if FEATURE_STRING_VARIABLES
+String parseTemplateAndCalculate(String& tmpString);
+#endif // if FEATURE_STRING_VARIABLES
+
String parseTemplate(String& tmpString);
String parseTemplate(String& tmpString,
@@ -36,10 +40,16 @@ String parseTemplate_padded(String& tmpString,
// valueFormat="transformation#justification"
void transformValue(
String & newString,
- uint8_t lineSize,
+ uint8_t lineSize,
String value,
String & valueFormat,
- const String& tmpString);
+ const String& tmpString
+ #if FEATURE_STRING_VARIABLES
+ , taskIndex_t taskIndex = INVALID_TASK_INDEX
+ , uint8_t valueIndex = INVALID_TASKVAR_INDEX
+ , String valueName = EMPTY_STRING
+ #endif // if FEATURE_STRING_VARIABLES
+);
diff --git a/src/src/Helpers/SystemVariables.cpp b/src/src/Helpers/SystemVariables.cpp
index ab9a1e60b..8147d24ce 100644
--- a/src/src/Helpers/SystemVariables.cpp
+++ b/src/src/Helpers/SystemVariables.cpp
@@ -224,6 +224,9 @@ String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) {
case SYSTM_HM_AM_0: return node_time.getTimeString_ampm(':', false, '0');
case SYSTM_HM_AM_SP: return node_time.getTimeString_ampm(':', false, ' ');
case SYSTZOFFSET: return node_time.getTimeZoneOffsetString();
+ #ifndef LIMIT_BUILD_SIZE
+ case SYSTZOFFSET_S: intvalue = static_cast(static_cast(node_time.getLocalUnixTime()) - static_cast(node_time.getUnixTime())); break;
+ #endif // ifndef LIMIT_BUILD_SIZE
case SYSWEEKDAY: intvalue = node_time.weekday(); break;
case SYSWEEKDAY_S: return node_time.weekday_str();
case SYSYEAR_0:
@@ -235,6 +238,9 @@ String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) {
case UNIXDAY: intvalue = node_time.getUnixTime() / 86400; break;
case UNIXDAY_SEC: intvalue = node_time.getUnixTime() % 86400; break;
case UNIXTIME: return String(node_time.getUnixTime());
+ #ifndef LIMIT_BUILD_SIZE
+ case UNIXTIME_LCL: return String(node_time.getLocalUnixTime());
+ #endif // ifndef LIMIT_BUILD_SIZE
case UPTIME: intvalue = getUptimeMinutes(); break;
case UPTIME_MS: return ull2String(getMicros64() / 1000);
#if FEATURE_ADC_VCC
@@ -645,6 +651,9 @@ const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum
case Enum::SYSTM_HM_AM_SP: return F("systm_hm_am_sp");
case Enum::SYSTM_HM_SP: return F("systm_hm_sp");
case Enum::SYSTZOFFSET: return F("systzoffset");
+ #ifndef LIMIT_BUILD_SIZE
+ case Enum::SYSTZOFFSET_S: return F("systzoffset_s");
+ #endif // ifndef LIMIT_BUILD_SIZE
case Enum::SYSWEEKDAY: return F("sysweekday");
case Enum::SYSWEEKDAY_S: return F("sysweekday_s");
case Enum::SYSYEAR: return F("sysyear");
@@ -657,6 +666,9 @@ const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum
case Enum::UNIXDAY: return F("unixday");
case Enum::UNIXDAY_SEC: return F("unixday_sec");
case Enum::UNIXTIME: return F("unixtime");
+ #ifndef LIMIT_BUILD_SIZE
+ case Enum::UNIXTIME_LCL: return F("unixtime_lcl");
+ #endif // ifndef LIMIT_BUILD_SIZE
case Enum::UPTIME: return F("uptime");
case Enum::UPTIME_MS: return F("uptime_ms");
case Enum::VCC: return F("vcc");
diff --git a/src/src/Helpers/SystemVariables.h b/src/src/Helpers/SystemVariables.h
index c2cc302a1..0b1c0df50 100644
--- a/src/src/Helpers/SystemVariables.h
+++ b/src/src/Helpers/SystemVariables.h
@@ -101,6 +101,9 @@ public:
SYSTM_HM_AM_SP,
SYSTM_HM_SP,
SYSTZOFFSET,
+ #ifndef LIMIT_BUILD_SIZE
+ SYSTZOFFSET_S,
+ #endif // ifndef LIMIT_BUILD_SIZE
SYSWEEKDAY,
SYSWEEKDAY_S,
SYSYEAR,
@@ -113,6 +116,9 @@ public:
UNIXDAY,
UNIXDAY_SEC,
UNIXTIME,
+ #ifndef LIMIT_BUILD_SIZE
+ UNIXTIME_LCL,
+ #endif // ifndef LIMIT_BUILD_SIZE
UPTIME,
UPTIME_MS,
VCC,
diff --git a/src/src/Helpers/TarStream.cpp b/src/src/Helpers/TarStream.cpp
index d11d4db8e..0e1b16084 100644
--- a/src/src/Helpers/TarStream.cpp
+++ b/src/src/Helpers/TarStream.cpp
@@ -32,10 +32,10 @@ TarFileInfo_struct::TarFileInfo_struct(const String fname,
*/
TarStream::TarStream() {}
-TarStream::TarStream(const String fileName)
+TarStream::TarStream(const String& fileName)
: _fileName(fileName) {}
-TarStream::TarStream(const String fileName, FileDestination_e destination)
+TarStream::TarStream(const String& fileName, FileDestination_e destination)
: _fileName(fileName), _destination(destination) {}
TarStream::~TarStream() {
diff --git a/src/src/Helpers/TarStream.h b/src/src/Helpers/TarStream.h
index abc0e5003..155c1225e 100644
--- a/src/src/Helpers/TarStream.h
+++ b/src/src/Helpers/TarStream.h
@@ -129,8 +129,8 @@ class TarStream : public Stream {
public:
TarStream();
- TarStream(const String fileName);
- TarStream(const String fileName,
+ TarStream(const String& fileName);
+ TarStream(const String& fileName,
FileDestination_e destination);
virtual ~TarStream();
diff --git a/src/src/Helpers/WebServer_commandHelper.cpp b/src/src/Helpers/WebServer_commandHelper.cpp
index d8f37ded5..de9706246 100644
--- a/src/src/Helpers/WebServer_commandHelper.cpp
+++ b/src/src/Helpers/WebServer_commandHelper.cpp
@@ -52,6 +52,9 @@ HandledWebCommand_result handle_command_from_web(EventValueSource::Enum source,
case ESPEasy_cmd_e::taskvaluesetandrun:
case ESPEasy_cmd_e::taskvaluetoggle:
case ESPEasy_cmd_e::let:
+ #if FEATURE_STRING_VARIABLES
+ case ESPEasy_cmd_e::letstr:
+ #endif // if FEATURE_STRING_VARIABLES
#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS
case ESPEasy_cmd_e::logportstatus:
#endif
diff --git a/src/src/PluginStructs/P023_data_struct.cpp b/src/src/PluginStructs/P023_data_struct.cpp
index 115a1c785..715dcd364 100644
--- a/src/src/PluginStructs/P023_data_struct.cpp
+++ b/src/src/PluginStructs/P023_data_struct.cpp
@@ -107,104 +107,103 @@ const char Plugin_023_myFont_Size[] PROGMEM = {
0x08 // DEL
};
-
-const char Plugin_023_myFont[][8] PROGMEM = {
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // SPACE
- { 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00 }, // !
- { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00 }, // "
- { 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00 }, // #
- { 0x00, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00 }, // $
- { 0x00, 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00 }, // %
- { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x00 }, // &
- { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00 }, // '
- { 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00 }, // (
- { 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00 }, // )
- { 0x00, 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00 }, // *
- { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // +
- { 0x00, 0xA0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ,
- { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // -
- { 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00 }, // .
- { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00 }, // /
- { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00 }, // 0
- { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00 }, // 1
- { 0x00, 0x62, 0x51, 0x49, 0x49, 0x46, 0x00, 0x00 }, // 2
- { 0x00, 0x22, 0x41, 0x49, 0x49, 0x36, 0x00, 0x00 }, // 3
- { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, 0x00 }, // 4
- { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00 }, // 5
- { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, 0x00 }, // 6
- { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, 0x00 }, // 7
- { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00 }, // 8
- { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00 }, // 9
- { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00 }, // :
- { 0x00, 0x00, 0xAC, 0x6C, 0x00, 0x00, 0x00, 0x00 }, // ;
- { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00 }, // <
- { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00 }, // =
- { 0x00, 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00 }, // >
- { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00 }, // ?
- { 0x00, 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, 0x00 }, // @
- { 0x00, 0x7E, 0x09, 0x09, 0x09, 0x7E, 0x00, 0x00 }, // A
- { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00 }, // B
- { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00 }, // C
- { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, 0x00 }, // D
- { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00 }, // E
- { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, 0x00 }, // F
- { 0x00, 0x3E, 0x41, 0x41, 0x51, 0x72, 0x00, 0x00 }, // G
- { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00 }, // H
- { 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, 0x00 }, // I
- { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00 }, // J
- { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00 }, // K
- { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, // L
- { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00, 0x00 }, // M
- { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00 }, // N
- { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00 }, // O
- { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00 }, // P
- { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00 }, // Q
- { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, 0x00 }, // R
- { 0x00, 0x26, 0x49, 0x49, 0x49, 0x32, 0x00, 0x00 }, // S
- { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00 }, // T
- { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00 }, // U
- { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00 }, // V
- { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, 0x00 }, // W
- { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00 }, // X
- { 0x00, 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00 }, // Y
- { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00 }, // Z
- { 0x00, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00 }, // [
- { 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00 }, // BACKSLASH
- { 0x00, 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, 0x00 }, // ]
- { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00 }, // ^
- { 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00 }, // _
- { 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 }, // `
- { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, 0x00 }, // a
- { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, 0x00 }, // b
- { 0x00, 0x38, 0x44, 0x44, 0x28, 0x00, 0x00, 0x00 }, // c
- { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00 }, // d
- { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00 }, // e
- { 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00, 0x00, 0x00 }, // f
- { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, 0x00, 0x00 }, // g
- { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00 }, // h
- { 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00 }, // i
- { 0x00, 0x80, 0x84, 0x7D, 0x00, 0x00, 0x00, 0x00 }, // j
- { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, // k
- { 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00 }, // l
- { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, 0x00 }, // m
- { 0x00, 0x7C, 0x08, 0x04, 0x7C, 0x00, 0x00, 0x00 }, // n
- { 0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00 }, // o
- { 0x00, 0xFC, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00 }, // p
- { 0x00, 0x18, 0x24, 0x24, 0xFC, 0x00, 0x00, 0x00 }, // q
- { 0x00, 0x00, 0x7C, 0x08, 0x04, 0x00, 0x00, 0x00 }, // r
- { 0x00, 0x48, 0x54, 0x54, 0x24, 0x00, 0x00, 0x00 }, // s
- { 0x00, 0x04, 0x7F, 0x44, 0x00, 0x00, 0x00, 0x00 }, // t
- { 0x00, 0x3C, 0x40, 0x40, 0x7C, 0x00, 0x00, 0x00 }, // u
- { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00 }, // v
- { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00 }, // w
- { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00 }, // x
- { 0x00, 0x1C, 0xA0, 0xA0, 0x7C, 0x00, 0x00, 0x00 }, // y
- { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x00 }, // z
- { 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x00 }, // {
- { 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00 }, // |
- { 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00, 0x00 }, // }
- { 0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x00 }, // ~
- { 0x00, 0x02, 0x05, 0x05, 0x02, 0x00, 0x00, 0x00 } // DEL
+const char Plugin_023_myFont[][5] PROGMEM = {
+ { 0x00, 0x00, 0x00, 0x00, 0x00 }, // SPACE
+ { 0x00, 0x5F, 0x00, 0x00, 0x00 }, // !
+ { 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
+ { 0x14, 0x7F, 0x14, 0x7F, 0x14 }, // #
+ { 0x24, 0x2A, 0x7F, 0x2A, 0x12 }, // $
+ { 0x23, 0x13, 0x08, 0x64, 0x62 }, // %
+ { 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
+ { 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
+ { 0x1C, 0x22, 0x41, 0x00, 0x00 }, // (
+ { 0x41, 0x22, 0x1C, 0x00, 0x00 }, // )
+ { 0x08, 0x2A, 0x1C, 0x2A, 0x08 }, // *
+ { 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
+ { 0xA0, 0x60, 0x00, 0x00, 0x00 }, // ,
+ { 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
+ { 0x60, 0x60, 0x00, 0x00, 0x00 }, // .
+ { 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
+ { 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0
+ { 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
+ { 0x62, 0x51, 0x49, 0x49, 0x46 }, // 2
+ { 0x22, 0x41, 0x49, 0x49, 0x36 }, // 3
+ { 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
+ { 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
+ { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
+ { 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
+ { 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
+ { 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
+ { 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
+ { 0x00, 0xAC, 0x6C, 0x00, 0x00 }, // ;
+ { 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
+ { 0x14, 0x14, 0x14, 0x14, 0x14 }, // =
+ { 0x41, 0x22, 0x14, 0x08, 0x00 }, // >
+ { 0x02, 0x01, 0x51, 0x09, 0x06 }, // ?
+ { 0x32, 0x49, 0x79, 0x41, 0x3E }, // @
+ { 0x7E, 0x09, 0x09, 0x09, 0x7E }, // A
+ { 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B
+ { 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C
+ { 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D
+ { 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E
+ { 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F
+ { 0x3E, 0x41, 0x41, 0x51, 0x72 }, // G
+ { 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H
+ { 0x41, 0x7F, 0x41, 0x00, 0x00 }, // I
+ { 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J
+ { 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K
+ { 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L
+ { 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M
+ { 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N
+ { 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O
+ { 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P
+ { 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q
+ { 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R
+ { 0x26, 0x49, 0x49, 0x49, 0x32 }, // S
+ { 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T
+ { 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U
+ { 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V
+ { 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W
+ { 0x63, 0x14, 0x08, 0x14, 0x63 }, // X
+ { 0x03, 0x04, 0x78, 0x04, 0x03 }, // Y
+ { 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z
+ { 0x7F, 0x41, 0x41, 0x00, 0x00 }, // [
+ { 0x02, 0x04, 0x08, 0x10, 0x20 }, // BACKSLASH
+ { 0x41, 0x41, 0x7F, 0x00, 0x00 }, // ]
+ { 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^
+ { 0x80, 0x80, 0x80, 0x80, 0x80 }, // _
+ { 0x01, 0x02, 0x04, 0x00, 0x00 }, // `
+ { 0x20, 0x54, 0x54, 0x54, 0x78 }, // a
+ { 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b
+ { 0x38, 0x44, 0x44, 0x28, 0x00 }, // c
+ { 0x38, 0x44, 0x44, 0x48, 0x7F }, // d
+ { 0x38, 0x54, 0x54, 0x54, 0x18 }, // e
+ { 0x08, 0x7E, 0x09, 0x02, 0x00 }, // f
+ { 0x18, 0xA4, 0xA4, 0xA4, 0x7C }, // g
+ { 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h
+ { 0x00, 0x7D, 0x00, 0x00, 0x00 }, // i
+ { 0x80, 0x84, 0x7D, 0x00, 0x00 }, // j
+ { 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k
+ { 0x41, 0x7F, 0x40, 0x00, 0x00 }, // l
+ { 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m
+ { 0x7C, 0x08, 0x04, 0x7C, 0x00 }, // n
+ { 0x38, 0x44, 0x44, 0x38, 0x00 }, // o
+ { 0xFC, 0x24, 0x24, 0x18, 0x00 }, // p
+ { 0x18, 0x24, 0x24, 0xFC, 0x00 }, // q
+ { 0x00, 0x7C, 0x08, 0x04, 0x00 }, // r
+ { 0x48, 0x54, 0x54, 0x24, 0x00 }, // s
+ { 0x04, 0x7F, 0x44, 0x00, 0x00 }, // t
+ { 0x3C, 0x40, 0x40, 0x7C, 0x00 }, // u
+ { 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v
+ { 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w
+ { 0x44, 0x28, 0x10, 0x28, 0x44 }, // x
+ { 0x1C, 0xA0, 0xA0, 0x7C, 0x00 }, // y
+ { 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z
+ { 0x08, 0x36, 0x41, 0x00, 0x00 }, // {
+ { 0x00, 0x7F, 0x00, 0x00, 0x00 }, // |
+ { 0x41, 0x36, 0x08, 0x00, 0x00 }, // }
+ { 0x02, 0x01, 0x01, 0x02, 0x01 }, // ~
+ { 0x02, 0x05, 0x05, 0x02, 0x00 } // DEL
};
@@ -420,13 +419,21 @@ void P023_data_struct::sendStrXY(const char *string, int X, int Y) {
maxPixels = 64;
}
+ constexpr int myFont_nrChars = NR_ELEMENTS(Plugin_023_myFont_Size);
+
while (*string && currentPixels < maxPixels) { // Prevent display overflow on the character level
+ auto index = *string - 0x20;
+ if (index < 0 || index >= myFont_nrChars) {
+ // Char not in font, just print a space
+ index = 0;
+ }
if (font_spacing == Spacing::optimized) {
- char_width = pgm_read_byte(&(Plugin_023_myFont_Size[*string - 0x20]));
+ char_width = pgm_read_byte(&(Plugin_023_myFont_Size[index]));
}
- for (i = 0; i < char_width && currentPixels + i < maxPixels; ++i) { // Prevent display overflow on the pixel-level
- sendChar(pgm_read_byte(Plugin_023_myFont[*string - 0x20] + i));
+ const char * baddr = Plugin_023_myFont[index];
+ for (i = 0; i < char_width && currentPixels < maxPixels; ++i,++currentPixels) { // Prevent display overflow on the pixel-level
+ sendChar((i == 0 || i > 5) ? 0x00 : pgm_read_byte(baddr + i - 1));
}
currentPixels += char_width;
string++;
diff --git a/src/src/PluginStructs/P116_data_struct.cpp b/src/src/PluginStructs/P116_data_struct.cpp
index 623b321f7..b803d1703 100644
--- a/src/src/PluginStructs/P116_data_struct.cpp
+++ b/src/src/PluginStructs/P116_data_struct.cpp
@@ -14,6 +14,7 @@ const __FlashStringHelper* ST77xx_type_toString(const ST77xx_type_e& device) {
# if P116_EXTRA_ST7735
case ST77xx_type_e::ST7735s_135x240: return F("ST7735 135 x 240px");
case ST77xx_type_e::ST7735s_172x320: return F("ST7735 172 x 320px");
+ case ST77xx_type_e::ST77xxs_170x320: return F("ST77xx 170 x 320px");
# endif // if P116_EXTRA_ST7735
case ST77xx_type_e::ST7789vw_240x320: return F("ST7789 240 x 320px");
case ST77xx_type_e::ST7789vw_240x240: return F("ST7789 240 x 240px");
@@ -78,6 +79,10 @@ void ST77xx_type_toResolution(const ST77xx_type_e& device,
x = 172;
y = 320;
break;
+ case ST77xx_type_e::ST77xxs_170x320:
+ x = 170;
+ y = 320;
+ break;
# endif // if P116_EXTRA_ST7735
case ST77xx_type_e::ST7796s_320x480:
x = 320;
@@ -189,6 +194,13 @@ bool P116_data_struct::plugin_init(struct EventStruct *event) {
initRoptions = INITR_BLACKTAB172x320; // 172x320px
}
+ // fall through
+ case ST77xx_type_e::ST77xxs_170x320:
+
+ if (initRoptions == 0xFF) {
+ initRoptions = INITR_BLACKTAB170x320; // 170x320px
+ }
+
// fall through
# endif // if P116_EXTRA_ST7735
case ST77xx_type_e::ST7735s_80x160:
diff --git a/src/src/PluginStructs/P116_data_struct.h b/src/src/PluginStructs/P116_data_struct.h
index 73e09b64e..deaf3cfd4 100644
--- a/src/src/PluginStructs/P116_data_struct.h
+++ b/src/src/PluginStructs/P116_data_struct.h
@@ -102,6 +102,7 @@ enum class ST77xx_type_e : uint8_t {
# if P116_EXTRA_ST7735
ST7735s_135x240 = 12u,
ST7735s_172x320 = 13u,
+ ST77xxs_170x320 = 14u,
# endif // if P116_EXTRA_ST7735
};
diff --git a/src/src/Static/WebStaticData.cpp b/src/src/Static/WebStaticData.cpp
index 6a8f813e7..12b0a93a9 100644
--- a/src/src/Static/WebStaticData.cpp
+++ b/src/src/Static/WebStaticData.cpp
@@ -13,17 +13,16 @@ String generate_external_URL(const String& fname, bool isEmbedded) {
// Generate some URL indicating static files which will need to be served with some cache-control header
return concat(F("static_"), Cache.fileCacheClearMoment) + '_' + fname;
}
+
#if FEATURE_ALTERNATIVE_CDN_URL
String cdn = get_CDN_url_custom();
if (!cdn.isEmpty()) {
cdn = parseTemplate(cdn); // Replace system variables.
return concat(cdn, fname); // cdn.endsWith('/') check done at save
- } else {
- #endif // if FEATURE_ALTERNATIVE_CDN_URL
- return concat(get_CDN_url_prefix(), fname);
- #if FEATURE_ALTERNATIVE_CDN_URL
- }
+ }
#endif // if FEATURE_ALTERNATIVE_CDN_URL
+
+ return concat(get_CDN_url_prefix(), fname);
}
void serve_CDN_CSS(const __FlashStringHelper * fname, const __FlashStringHelper * fname_alt, bool isEmbedded) {
@@ -126,12 +125,30 @@ void serve_CSS(CSSfiles_e cssfile) {
void serve_favicon() {
#ifdef WEBSERVER_FAVICON_CDN
+ addHtml(F(""));
+
+ /*
addHtml(F("');
+*/
#endif
+ #ifndef LIMIT_BUILD_SIZE
+ addHtml(F("');
+
+/*
+
+*/
+#endif
}
void serve_JS(JSfiles_e JSfile) {
diff --git a/src/src/Static/WebStaticData.h b/src/src/Static/WebStaticData.h
index a0caeddae..d5a5f3516 100644
--- a/src/src/Static/WebStaticData.h
+++ b/src/src/Static/WebStaticData.h
@@ -366,7 +366,28 @@ static const char jsSplitMultipleFields[] PROGMEM = {
};
#ifdef WEBSERVER_INCLUDE_JS
-static const char DATA_UPDATE_SENSOR_VALUES_DEVICE_PAGE_JS[] PROGMEM = {0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x20,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x65,0x2c,0x61,0x29,0x7b,0x76,0x61,0x72,0x20,0x73,0x2c,0x6c,0x2c,0x6f,0x3d,0x30,0x3b,0x69,0x73,0x4e,0x61,0x4e,0x28,0x61,0x29,0x26,0x26,0x28,0x61,0x3d,0x31,0x29,0x2c,0x6e,0x75,0x6c,0x6c,0x3d,0x3d,0x65,0x26,0x26,0x28,0x65,0x3d,0x31,0x65,0x33,0x29,0x3b,0x76,0x61,0x72,0x20,0x6e,0x3d,0x73,0x65,0x74,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x29,0x7b,0x6f,0x3e,0x30,0x3f,0x63,0x6c,0x65,0x61,0x72,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x6e,0x29,0x3a,0x2b,0x2b,0x61,0x3e,0x31,0x3f,0x6f,0x3d,0x31,0x3a,0x28,0x66,0x65,0x74,0x63,0x68,0x28,0x22,0x2f,0x6a,0x73,0x6f,0x6e,0x3f,0x76,0x69,0x65,0x77,0x3d,0x73,0x65,0x6e,0x73,0x6f,0x72,0x75,0x70,0x64,0x61,0x74,0x65,0x22,0x29,0x2e,0x74,0x68,0x65,0x6e,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x61,0x29,0x7b,0x76,0x61,0x72,0x20,0x6f,0x3b,0x32,0x30,0x30,0x3d,0x3d,0x3d,0x61,0x2e,0x73,0x74,0x61,0x74,0x75,0x73,0x3f,0x61,0x2e,0x6a,0x73,0x6f,0x6e,0x28,0x29,0x2e,0x74,0x68,0x65,0x6e,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x61,0x29,0x7b,0x66,0x6f,0x72,0x28,0x65,0x3d,0x61,0x2e,0x54,0x54,0x4c,0x2c,0x73,0x3d,0x30,0x3b,0x73,0x3c,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x2e,0x6c,0x65,0x6e,0x67,0x74,0x68,0x3b,0x73,0x2b,0x2b,0x29,0x69,0x66,0x28,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x68,0x61,0x73,0x4f,0x77,0x6e,0x50,0x72,0x6f,0x70,0x65,0x72,0x74,0x79,0x28,0x22,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x22,0x29,0x29,0x66,0x6f,0x72,0x28,0x6c,0x3d,0x30,0x3b,0x6c,0x3c,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x2e,0x6c,0x65,0x6e,0x67,0x74,0x68,0x3b,0x6c,0x2b,0x2b,0x29,0x74,0x72,0x79,0x7b,0x6f,0x3d,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x7d,0x63,0x61,0x74,0x63,0x68,0x28,0x65,0x29,0x7b,0x6f,0x3d,0x65,0x2e,0x6e,0x61,0x6d,0x65,0x7d,0x66,0x69,0x6e,0x61,0x6c,0x6c,0x79,0x7b,0x69,0x66,0x28,0x22,0x54,0x79,0x70,0x65,0x45,0x72,0x72,0x6f,0x72,0x22,0x21,0x3d,0x3d,0x6f,0x29,0x7b,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x3d,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x2c,0x64,0x65,0x63,0x69,0x6d,0x61,0x6c,0x73,0x56,0x61,0x6c,0x75,0x65,0x3d,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x4e,0x72,0x44,0x65,0x63,0x69,0x6d,0x61,0x6c,0x73,0x2c,0x64,0x65,0x63,0x69,0x6d,0x61,0x6c,0x73,0x56,0x61,0x6c,0x75,0x65,0x3c,0x32,0x35,0x35,0x26,0x26,0x28,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x3d,0x70,0x61,0x72,0x73,0x65,0x46,0x6c,0x6f,0x61,0x74,0x28,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x29,0x2e,0x74,0x6f,0x46,0x69,0x78,0x65,0x64,0x28,0x64,0x65,0x63,0x69,0x6d,0x61,0x6c,0x73,0x56,0x61,0x6c,0x75,0x65,0x29,0x29,0x3b,0x76,0x61,0x72,0x20,0x72,0x3d,0x22,0x76,0x61,0x6c,0x75,0x65,0x5f,0x22,0x2b,0x28,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2b,0x22,0x5f,0x22,0x2b,0x28,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2c,0x74,0x3d,0x22,0x76,0x61,0x6c,0x75,0x65,0x6e,0x61,0x6d,0x65,0x5f,0x22,0x2b,0x28,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2b,0x22,0x5f,0x22,0x2b,0x28,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2c,0x75,0x3d,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x2e,0x67,0x65,0x74,0x45,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x42,0x79,0x49,0x64,0x28,0x72,0x29,0x2c,0x63,0x3d,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x2e,0x67,0x65,0x74,0x45,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x42,0x79,0x49,0x64,0x28,0x74,0x29,0x3b,0x6e,0x75,0x6c,0x6c,0x21,0x3d,0x3d,0x75,0x26,0x26,0x28,0x75,0x2e,0x69,0x6e,0x6e,0x65,0x72,0x48,0x54,0x4d,0x4c,0x3d,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x29,0x2c,0x6e,0x75,0x6c,0x6c,0x21,0x3d,0x3d,0x63,0x26,0x26,0x28,0x63,0x2e,0x69,0x6e,0x6e,0x65,0x72,0x48,0x54,0x4d,0x4c,0x3d,0x61,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x73,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x4e,0x61,0x6d,0x65,0x2b,0x22,0x3a,0x22,0x29,0x7d,0x7d,0x65,0x3d,0x61,0x2e,0x54,0x54,0x4c,0x2c,0x63,0x6c,0x65,0x61,0x72,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x6e,0x29,0x2c,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x65,0x2c,0x30,0x29,0x7d,0x29,0x3a,0x63,0x6f,0x6e,0x73,0x6f,0x6c,0x65,0x2e,0x6c,0x6f,0x67,0x28,0x22,0x4c,0x6f,0x6f,0x6b,0x73,0x20,0x6c,0x69,0x6b,0x65,0x20,0x74,0x68,0x65,0x72,0x65,0x20,0x77,0x61,0x73,0x20,0x61,0x20,0x70,0x72,0x6f,0x62,0x6c,0x65,0x6d,0x2e,0x20,0x53,0x74,0x61,0x74,0x75,0x73,0x20,0x43,0x6f,0x64,0x65,0x3a,0x20,0x22,0x2b,0x61,0x2e,0x73,0x74,0x61,0x74,0x75,0x73,0x29,0x7d,0x29,0x2e,0x63,0x61,0x74,0x63,0x68,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x61,0x29,0x7b,0x63,0x6f,0x6e,0x73,0x6f,0x6c,0x65,0x2e,0x6c,0x6f,0x67,0x28,0x61,0x2e,0x6d,0x65,0x73,0x73,0x61,0x67,0x65,0x29,0x2c,0x65,0x3d,0x35,0x65,0x33,0x2c,0x63,0x6c,0x65,0x61,0x72,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x6e,0x29,0x2c,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x65,0x2c,0x30,0x29,0x7d,0x29,0x2c,0x6f,0x3d,0x31,0x29,0x7d,0x2c,0x65,0x29,0x7d,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x31,0x65,0x33,0x2c,0x30,0x29,0x3b,0};
+static const char DATA_UPDATE_SENSOR_VALUES_DEVICE_PAGE_JS[] PROGMEM = {
+ "function elId(e){return document.getElementById(e)}"
+ "function loopDeLoop(e,s){var o,a,n=0;isNaN(s)&&(s=1),null==e&&(e=1e3),"
+ "i=setInterval(function(){if(n>0){clearInterval(i);return}"
+ "++s>1||fetch('/json?view=sensorupdate').then(function(s){var n;if(200!==s.status){console.log('Looks like there was a problem. Status Code: '+s.status);return}"
+ "s.json().then(function(s){e=s.TTL;"
+ #if (defined(FEATURE_TASKVALUE_UNIT_OF_MEASURE) && FEATURE_TASKVALUE_UNIT_OF_MEASURE) || (defined(FEATURE_STRING_VARIABLES) && FEATURE_STRING_VARIABLES)
+ "var r=void 0===s.ShowUoM||s.ShowUoM&&'false'!==s.ShowUoM;"
+ #endif // if (defined(FEATURE_TASKVALUE_UNIT_OF_MEASURE) && FEATURE_TASKVALUE_UNIT_OF_MEASURE) || (defined(FEATURE_STRING_VARIABLES) && FEATURE_STRING_VARIABLES)
+ "for(o=0;ofirst.startsWith(search) && it->first.endsWith(postfix)) {
+ String valueName = it->first.substring(search.length(), it->first.indexOf('-'));
+ const String key2 = strformat(F(TASK_VALUE_NAME_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str());
+ const String vname2 = getCustomStringVar(key2);
+ const String keyUoM = strformat(F(TASK_VALUE_UOM_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str());
+ const String uom = getCustomStringVar(keyUoM);
+ if (!vname2.isEmpty()) {
+ valueName = vname2;
+ }
+ if (!it->second.isEmpty()) {
+ String value(it->second);
+ value = parseTemplateAndCalculate(value);
+ String presentation = getCustomStringVar(strformat(F(TASK_VALUE_PRESENTATION_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str()));
+ if (!uom.isEmpty()) {
+ value = strformat(F("%s %s"), value.c_str(), uom.c_str());
+ }
+ if (!presentation.isEmpty()) {
+ stripEscapeCharacters(presentation);
+ presentation.replace(F("%value%"), value);
+ value = parseTemplate(presentation);
+ }
+ pluginWebformShowValue(
+ x,
+ varNr,
+ valueName,
+ value);
+ ++varNr;
+ }
+ }
+ ++it;
+ }
+ }
+ #endif // if FEATURE_STRING_VARIABLES
}
}
}
@@ -1443,12 +1501,17 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi
PluginCall(PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, &TempEvent, dummy); // Show extra settings for Error State Value options
}
- addRowLabel(F("Single event with all values"));
- addCheckBox(F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex));
+ addFormCheckBox(F("Single event with all values"), F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex));
addFormNote(strformat(
F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"),
getTaskDeviceName(taskIndex).c_str()));
+ #if FEATURE_STRING_VARIABLES
+ addFormCheckBox(F("Show derived values"), F("TSDV"), Settings.ShowDerivedTaskValues(taskIndex));
+ addFormCheckBox(F("Event & Log derived values"), F("TELD"), Settings.EventAndLogDerivedTaskValues(taskIndex));
+ addFormCheckBox(F("Send derived values"), F("TSND"), Settings.SendDerivedTaskValues(taskIndex), true); // FIXME enable when SendDerivedTaskValues feature is implemented
+ #endif // if FEATURE_STRING_VARIABLES
+
bool separatorAdded = false;
for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++)
diff --git a/src/src/WebServer/HTML_wrappers.cpp b/src/src/WebServer/HTML_wrappers.cpp
index 43ea53a9c..1820cc801 100644
--- a/src/src/WebServer/HTML_wrappers.cpp
+++ b/src/src/WebServer/HTML_wrappers.cpp
@@ -441,10 +441,30 @@ void addHtmlFloat(const float& value, unsigned int nrDecimals) {
addHtml(toString(value, nrDecimals));
}
+void addHtmlFloat_NaN_toNull(const float& value, unsigned int nrDecimals) {
+ String res;
+ if (toValidString(res, value, nrDecimals)) {
+ addHtml(res);
+ } else {
+ addHtml(F("null"));
+ }
+}
+
+
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
void addHtmlFloat(const double& value, unsigned int nrDecimals) {
addHtml(doubleToString(value, nrDecimals));
}
+
+void addHtmlFloat_NaN_toNull(const double& value, unsigned int nrDecimals) {
+ String res;
+ if (doubleToValidString(res, value, nrDecimals)) {
+ addHtml(res);
+ } else {
+ addHtml(F("null"));
+ }
+}
+
#endif
@@ -496,17 +516,17 @@ void addHtmlAttribute(const __FlashStringHelper * label, const __FlashStringHelp
void addHtmlAttribute(const __FlashStringHelper * label, const String& value) {
addHtml(' ');
addHtml(label);
- addHtml(F("='"));
+ addHtml('=', '\'');
addEncodedHtml(value);
- addHtml(F("' "));
+ addHtml('\'', ' ');
}
void addHtmlAttribute(const String& label, const String& value) {
addHtml(' ');
addHtml(label);
- addHtml(F("='"));
+ addHtml('=', '\'');
addEncodedHtml(value);
- addHtml(F("' "));
+ addHtml('\'', ' ');
}
void addDisabled() {
diff --git a/src/src/WebServer/HTML_wrappers.h b/src/src/WebServer/HTML_wrappers.h
index fa445b3d6..005393a84 100644
--- a/src/src/WebServer/HTML_wrappers.h
+++ b/src/src/WebServer/HTML_wrappers.h
@@ -139,8 +139,10 @@ void addHtmlInt(uint32_t int_val);
void addHtmlInt(int64_t int_val);
void addHtmlInt(uint64_t int_val);
void addHtmlFloat(const float& value, unsigned int nrDecimals = 2u);
+void addHtmlFloat_NaN_toNull(const float& value, unsigned int nrDecimals = 2u);
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
void addHtmlFloat(const double& value, unsigned int nrDecimals = 2u);
+void addHtmlFloat_NaN_toNull(const double& value, unsigned int nrDecimals = 2u);
#endif
void addEncodedHtml(const __FlashStringHelper * html);
diff --git a/src/src/WebServer/I2C_Scanner.cpp b/src/src/WebServer/I2C_Scanner.cpp
index d69613446..7ca8c567e 100644
--- a/src/src/WebServer/I2C_Scanner.cpp
+++ b/src/src/WebServer/I2C_Scanner.cpp
@@ -287,6 +287,7 @@ String getKnownI2Cdevice(uint8_t address) {
case 0x41:
case 0x42:
case 0x43:
+ case 0x47:
result += F("INA219");
break;
case 0x44:
@@ -294,7 +295,7 @@ String getKnownI2Cdevice(uint8_t address) {
result += F("SHT30/31/35,INA219,SHT4x");
break;
case 0x46:
- result += F("SHT4x");
+ result += F("SHT4x,INA219");
break;
case 0x48:
result += F("PCF8591,ADS1x15,LM75A,INA219,TMP117,VEML6030");
diff --git a/src/src/WebServer/JSON.cpp b/src/src/WebServer/JSON.cpp
index 3e3487fbb..08b86a6e1 100644
--- a/src/src/WebServer/JSON.cpp
+++ b/src/src/WebServer/JSON.cpp
@@ -453,23 +453,73 @@ void handle_json()
for (uint8_t x = 0; x < valueCount; x++)
{
- addHtml('{');
- uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x);
- const String value = formatUserVarNoCheck(&TempEvent, x);
-
+ uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x);
+ String value = formatUserVarNoCheck(&TempEvent, x);
+ #if FEATURE_STRING_VARIABLES
+ bool hasPresentation;
+ const String presentation = formatUserVarForPresentation(&TempEvent, x, hasPresentation, value, DeviceIndex);
+ #endif // if FEATURE_STRING_VARIABLES
+
if (mustConsiderAsJSONString(value)) {
// Flag as not to treat as a float
nrDecimals = 255;
}
- stream_next_json_object_value(F("ValueNumber"), x + 1);
- stream_next_json_object_value(F("Name"), Cache.getTaskDeviceValueName(TaskIndex, x));
- stream_next_json_object_value(F("NrDecimals"), nrDecimals);
- stream_last_json_object_value(F("Value"), value);
+ handle_json_stream_task_value_data(x + 1,
+ Cache.getTaskDeviceValueName(TaskIndex, x),
+ nrDecimals,
+ value,
+ #if FEATURE_STRING_VARIABLES
+ presentation,
+ #else // if FEATURE_STRING_VARIABLES
+ EMPTY_STRING,
+ #endif // if FEATURE_STRING_VARIABLES
+ EMPTY_STRING, // TODO: pass UoM
+ x < (valueCount - 1));
+ }
+ #if FEATURE_STRING_VARIABLES
+ if (Settings.ShowDerivedTaskValues(TaskIndex)) {
+ int varNr = VARS_PER_TASK;
+ String taskName = getTaskDeviceName(TaskIndex);
+ taskName.toLowerCase();
+ String search = strformat(F(TASK_VALUE_DERIVED_PREFIX_TEMPLATE), taskName.c_str(), FsP(F("X")));
+ const String postfix = search.substring(search.indexOf('X') + 1);
+ search = search.substring(0, search.indexOf('X')); // Cut off left of valuename
- if (x < (valueCount - 1)) {
- stream_comma_newline();
+ auto it = customStringVar.begin();
+ while (it != customStringVar.end()) {
+ if (it->first.startsWith(search) && it->first.endsWith(postfix)) {
+ String valueName = it->first.substring(search.length(), it->first.indexOf('-'));
+ const String key2 = strformat(F(TASK_VALUE_NAME_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str());
+ const String vname2 = getCustomStringVar(key2);
+ const String keyUoM = strformat(F(TASK_VALUE_UOM_PREFIX_TEMPLATE), taskName.c_str(), valueName.c_str());
+ const String uom = getCustomStringVar(keyUoM); // FIXME add check for UoM setting
+ if (!vname2.isEmpty()) {
+ valueName = vname2;
+ }
+ if (!it->second.isEmpty()) {
+ String value(it->second);
+ stripEscapeCharacters(value);
+ value = parseTemplate(value);
+ ESPEASY_RULES_FLOAT_TYPE floatvalue{};
+ uint8_t nrDecimals = 255; // FIXME Use the minimal number of decimals needed
+ bool hasPresentation;
+ const String presentation = formatUserVarForPresentation(&TempEvent, INVALID_TASKVAR_INDEX, hasPresentation, value, DeviceIndex, valueName);
+
+ stream_comma_newline(); // Push out a comma and newline
+ handle_json_stream_task_value_data(varNr + 1,
+ valueName,
+ nrDecimals,
+ value,
+ presentation,
+ uom,
+ false); // No comma here
+ ++varNr;
+ }
+ }
+ ++it;
}
}
+ #endif // if FEATURE_STRING_VARIABLES
addHtml(F("],\n"));
}
@@ -573,6 +623,31 @@ void handle_json()
STOP_TIMER(HANDLE_SERVING_WEBPAGE_JSON);
}
+void handle_json_stream_task_value_data(uint16_t valueNumber,
+ const String & valueName,
+ uint8_t nrDecimals,
+ const String & value,
+ const String & presentation,
+ const String & uom,
+ bool appendComma) {
+ addHtml('{');
+ stream_next_json_object_value(F("ValueNumber"), valueNumber);
+ stream_next_json_object_value(F("Name"), valueName);
+ stream_next_json_object_value(F("NrDecimals"), nrDecimals);
+ #if FEATURE_STRING_VARIABLES
+ if (!presentation.isEmpty()) {
+ stream_next_json_object_value(F("Presentation"), presentation);
+ }
+ #endif // if FEATURE_STRING_VARIABLES
+ if (!uom.isEmpty()) {
+ stream_next_json_object_value(F("UoM"), uom);
+ }
+ stream_last_json_object_value(F("Value"), value);
+
+ if (appendComma) {
+ stream_comma_newline();
+ }
+}
// ********************************************************************************
// JSON formatted timing statistics
// ********************************************************************************
@@ -696,10 +771,9 @@ void stream_to_json_object_value(const __FlashStringHelper * object, const Stri
}
void stream_to_json_object_value(const String& object, const String& value) {
- addHtml(strformat(
- F("\"%s\":%s"),
- object.c_str(),
- to_json_value(value).c_str()));
+ addHtml(wrap_String(object, '"', '"'));
+ addHtml(':');
+ addHtml(to_json_value(value));
}
void stream_to_json_object_value(const __FlashStringHelper * object, int value) {
@@ -707,10 +781,9 @@ void stream_to_json_object_value(const __FlashStringHelper * object, int value)
}
void stream_to_json_object_value(const String& object, int value) {
- addHtml(strformat(
- F("\"%s\":%d"),
- object.c_str(),
- value));
+ addHtml(wrap_String(object, '"', '"'));
+ addHtml(':');
+ addHtmlInt(value);
}
String jsonBool(bool value) {
diff --git a/src/src/WebServer/JSON.h b/src/src/WebServer/JSON.h
index ba7009907..045b1f93a 100644
--- a/src/src/WebServer/JSON.h
+++ b/src/src/WebServer/JSON.h
@@ -17,6 +17,14 @@ void handle_csvval();
// ********************************************************************************
void handle_json();
+void handle_json_stream_task_value_data(uint16_t valueNumber,
+ const String & valueName,
+ uint8_t nrDecimals,
+ const String & value,
+ const String & presentation,
+ const String & uom,
+ bool appendComma);
+
// ********************************************************************************
// JSON formatted timing statistics
// ********************************************************************************
diff --git a/src/src/WebServer/SysVarPage.cpp b/src/src/WebServer/SysVarPage.cpp
index e5a73daba..155e936e0 100644
--- a/src/src/WebServer/SysVarPage.cpp
+++ b/src/src/WebServer/SysVarPage.cpp
@@ -13,6 +13,9 @@
# include "../Helpers/Numerical.h"
# include "../Helpers/StringConverter.h"
+# if FEATURE_STRING_VARIABLES
+# include "../Helpers/StringParser.h"
+# endif // if
# include "../Helpers/SystemVariables.h"
@@ -58,12 +61,28 @@ void handle_sysvars() {
html_TD();
} else {
uint32_t tmpVar{};
+
for (auto it = customFloatVar.begin(); it != customFloatVar.end(); ++it) {
const bool isv_ = !validUIntFromString(it->first, tmpVar);
addSysVar_html(strformat(F("%%%s%s%%"), FsP(isv_ ? F("v_") : F("v")), it->first.c_str()), false);
}
}
+ # if FEATURE_STRING_VARIABLES
+ addTableSeparator(F("String Variables"), 3, 3);
+
+ if (customStringVar.empty()) {
+ html_TR_TD();
+ addHtml(F("No string variables set"));
+ html_TD();
+ html_TD();
+ } else {
+ for (auto it = customStringVar.begin(); it != customStringVar.end(); ++it) {
+ addSysVar_html(strformat(F("[str#%s]"), it->first.c_str()), false);
+ }
+ }
+ # endif // if FEATURE_STRING_VARIABLES
+
addTableSeparator(F("Constants"), 3, 3);
{
const SystemVariables::Enum vars[] = {
@@ -88,9 +107,9 @@ void handle_sysvars() {
SystemVariables::DNS,
SystemVariables::DNS_1,
SystemVariables::DNS_2,
-#if FEATURE_USE_IPV6
+# if FEATURE_USE_IPV6
SystemVariables::IP6_LOCAL,
-#endif
+# endif // if FEATURE_USE_IPV6
SystemVariables::RSSI,
SystemVariables::SSID,
SystemVariables::BSSID,
@@ -183,6 +202,9 @@ void handle_sysvars() {
SystemVariables::UPTIME,
SystemVariables::UPTIME_MS,
SystemVariables::UNIXTIME,
+ # ifndef LIMIT_BUILD_SIZE
+ SystemVariables::UNIXTIME_LCL,
+ # endif // ifndef LIMIT_BUILD_SIZE
SystemVariables::UNIXDAY,
SystemVariables::UNIXDAY_SEC,
};
@@ -219,6 +241,9 @@ void handle_sysvars() {
SystemVariables::SYSWEEKDAY,
SystemVariables::SYSWEEKDAY_S,
SystemVariables::SYSTZOFFSET,
+ # ifndef LIMIT_BUILD_SIZE
+ SystemVariables::SYSTZOFFSET_S,
+ # endif // ifndef LIMIT_BUILD_SIZE
};
addSysVar_enum_html(vars, NR_ELEMENTS(vars));
}
@@ -357,7 +382,7 @@ void handle_sysvars() {
}
# endif // ifndef BUILD_NO_SPECIAL_CHARACTERS_STRINGCONVERTER
{
- addTableSeparator(F("Standard Conversions"), 3, 2);
+ addTableSeparator(F("Standard Conversions"), 3, 3);
const __FlashStringHelper *StdConversions[] = {
F("Wind Dir.: %c_w_dir%(123.4)"),
@@ -367,24 +392,31 @@ void handle_sysvars() {
F("Altitude(air,sea): %c_alt_pres_sea%(850,1000)"),
F("PressureElevation(air,alt): %c_sea_pres_alt%(850,1350.03)"),
- // addFormSeparator(3,
+ F(""), // addFormSeparator(3,
F("cm to imperial: %c_cm2imp%(190)"),
F("mm to imperial: %c_mm2imp%(1900)"),
- // addFormSeparator(3,
+ F(""), // addFormSeparator(3,
F("Mins to days: %c_m2day%(1900)"),
F("Mins to dh: %c_m2dh%(1900)"),
F("Mins to dhm: %c_m2dhm%(1900)"),
F("Mins to hcm: %c_m2hcm%(482)"),
F("Secs to dhms: %c_s2dhms%(100000)"),
+# if FEATURE_STRING_VARIABLES
+ F("Timestamp to date/time: %c_ts2date%(%unixtime_lcl%)"),
+ F("Timestamp to date/time am/pm: %c_ts2date%(%unixtime_lcl%,1)"),
+ F("Timestamp to weekday: %c_ts2wday%(%unixtime_lcl%)"),
+# endif // if FEATURE_STRING_VARIABLES
+
+ F(""), // addFormSeparator(3,
+# ifndef LIMIT_BUILD_SIZE
F("Random: %c_random%(0, 100)"),
+# endif // ifndef LIMIT_BUILD_SIZE
+ F("To HEX: %c_2hex%(100000) or: %c_2hex%(100000,6)"),
- // addFormSeparator(3,
- F("To HEX: %c_2hex%(100000)"),
-
- #if FEATURE_ESPEASY_P2P
- // addFormSeparator(3,
+# if FEATURE_ESPEASY_P2P
+ F(""), // addFormSeparator(3,
F("Unit to IP: %c_u2ip%(%unit%, 2)"),
F("Unit to Name: %c_uname%(%unit%)"),
F("Unit to Age: %c_uage%(%unit%)"),
@@ -393,21 +425,58 @@ void handle_sysvars() {
F("Unit to Load: %c_uload%(%unit%)"),
F("Unit to ESP-type: %c_utype%(%unit%)"),
F("Unit to ESP-type-string: %c_utypestr%(%unit%)"),
- #endif // if FEATURE_ESPEASY_P2P
+# endif // if FEATURE_ESPEASY_P2P
+# if FEATURE_STRING_VARIABLES
+ F(""), // addFormSeparator(3,
+ F("Check if numeric value (test:'123'): %c_isnum%(test)"),
+ F("Format (testf:'Out $6.2f M$g'): %c_strf%(testf,123.45,2)"),
+# endif // if FEATURE_STRING_VARIABLES
};
- for (unsigned int i = 0; i < NR_ELEMENTS(StdConversions); ++i) {
- if ((i == 6) ||
- (i == 8) ||
- (i == 13)
- #if FEATURE_ESPEASY_P2P
- || (i == 14)
- #endif // if FEATURE_ESPEASY_P2P
- ) {
- addFormSeparator(3);
- }
- addSysVar_html(StdConversions[i]);
+# if FEATURE_STRING_VARIABLES
+ const bool has_test = hasCustomStringVar(F("test"));
+ const bool has_testf = hasCustomStringVar(F("testf"));
+
+ // Save current values
+ String test, testf;
+
+ if (has_test) {
+ test = getCustomStringVar(F("test"));
}
+
+ if (has_testf) {
+ testf = getCustomStringVar(F("testf"));
+ }
+
+ setCustomStringVar(F("test"), F("123"));
+ setCustomStringVar(F("testf"), F("Out $6.2f M$g"));
+# endif // if FEATURE_STRING_VARIABLES
+ constexpr uint16_t nrStdConv = NR_ELEMENTS(StdConversions);
+
+ for (uint16_t i = 0; i < nrStdConv; ++i) {
+ const String s(StdConversions[i]);
+
+ if (s.isEmpty()) { addFormSeparator(3); }
+ else { addSysVar_html(s); }
+ }
+# if FEATURE_STRING_VARIABLES
+
+ // Restore values
+ if (has_test) {
+ setCustomStringVar(F("test"), test);
+ }
+ else {
+ clearCustomStringVar(F("test"));
+ }
+
+ if (has_testf) {
+ setCustomStringVar(F("testf"), testf);
+ }
+ else {
+ clearCustomStringVar(F("testf"));
+ }
+
+# endif // if FEATURE_STRING_VARIABLES
}
html_end_table();
html_end_form();
@@ -453,6 +522,9 @@ void addSysVar_html(String input, bool isSpecialChar) {
parseSystemVariables(input, URLencoded);
parseStandardConversions(input, URLencoded);
+ # if FEATURE_STRING_VARIABLES
+ input = parseTemplate_padded(input, 0, URLencoded);
+ # endif // if FEATURE_STRING_VARIABLES
html_TD();
addHtml(input);
diff --git a/static/espeasy.js b/static/espeasy.js
index a02b67116..49d15cdc9 100644
--- a/static/espeasy.js
+++ b/static/espeasy.js
@@ -6,9 +6,9 @@ var commonAtoms = ["And", "Or"];
var commonKeywords = ["If", "Else", "Elseif", "Endif"];
var commonCommands = ["AccessInfo", "Background", "Build", "ClearAccessBlock", "ClearRTCam", "Config", "ControllerDisable",
"ControllerEnable", "DateTime", "Debug", "Dec", "DeepSleep", "DisablePriorityTask", "DNS", "DST", "EraseSDKWiFi", "ExecuteRules", "FactoryReset", "Gateway", "I2Cscanner", "Inc",
- "IP", "Let", "Load", "LogEntry", "LogPortStatus", "LoopTimerSet", "LoopTimerSet_ms", "LoopTimerSetAndRun", "LoopTimerSetAndRun_ms", "MemInfo", "MemInfoDetail", "Name", "Password", "PostToHTTP", "Publish", "PublishR",
- "Reboot", "Save", "SendTo", "SendToHTTP", "SendToUDP", "Settings", "Subnet", "Subscribe", "TaskClear", "TaskClearAll",
- "TaskDisable", "TaskEnable", "TaskRun", "TaskValueSet", "TaskValueSetAndRun", "TimerPause", "TimerResume", "TimerSet", "TimerSet_ms", "TimeZone",
+ "IP", "Let", "LetStr", "Load", "LogEntry", "LogPortStatus", "LoopTimerSet", "LoopTimerSet_ms", "LoopTimerSetAndRun", "LoopTimerSetAndRun_ms", "MemInfo", "MemInfoDetail", "Name", "Password", "PostToHTTP", "Publish", "PublishR",
+ "Reboot", "Save", "SendTo", "SendToHTTP", "SendToUDP", "SendToUDPMix", "Settings", "Subnet", "Subscribe", "TaskClear", "TaskClearAll",
+ "TaskDisable", "TaskEnable", "TaskRun", "TaskValueSet", "TaskValueSetAndRun", "TaskValueSetDerived", "TaskValueSetPresentation", "TimerPause", "TimerResume", "TimerSet", "TimerSet_ms", "TimeZone",
"UdpPort", "UdpTest", "Unit", "UseNTP", "WdConfig", "WdRead", "WiFi", "WiFiAllowAP", "WiFiAPMode", "WiFiConnect", "WiFiDisconnect", "WiFiKey",
"WiFiKey2", "WiFiMode", "WiFiScan", "WiFiSSID", "WiFiSSID2", "WiFiSTAMode",
"Event", "AsyncEvent",
@@ -33,7 +33,7 @@ var commonPlugins = [
"PCFGPIO", "PCFGPIOToggle", "PCFLongPulse", "PCFLongPulse_ms", "PCFPulse", "Status,PCF", "Monitor,PCF",
"MonitorRange,PCF", "UnMonitorRange,PCF", "UnMonitor,PCF", "PCFGPIORange", "PCFGPIOpattern", "PCFMode", "PCFmodeRange",
//P020/P044
- "SerialSend", "SerialSendMix", "Ser2NetClientSend",
+ "SerialSend", "SerialSendMix", "Ser2NetClientSend", "SerialSend_test",
//P022
"pcapwm", "pcafrq", "mode2",
//P023
@@ -129,6 +129,10 @@ var commonPlugins = [
"as3935", "as3935,clearstats", "as3935,calibrate", "as3935,setgain,", "as3935,setnf,", "as3935,setwd,", "as3925,setsrej,",
//P175(P053)
/*"pmsx003", "pmsx003,wake", "pmsx003,sleep", "pmsx003,reset",*/
+ //P178
+ "lu9685", "lu9685,servo,", "lu9685,enable,", "lu9685,disable,", "lu9685,setrange,",
+ //P180
+ "geni2c", "geni2c,cmd,", "geni2c,exec,", "geni2c,log,",
];
var pluginDispKind = [
//P095
@@ -165,20 +169,20 @@ var AnythingElse = [
"%systm_hm_0%", "%systm_hm_sp%", "%systime_am%", "%systime_am_0%", "%systime_am_sp%", "%systm_hm_am%", "%systm_hm_am_0%", "%systm_hm_am_sp%",
"%lcltime%", "%sunrise%", "%s_sunrise%", "%m_sunrise%", "%sunset%", "%s_sunset%", "%m_sunset%", "%lcltime_am%",
"%syshour%", "%syshour_0%", "%sysmin%", "%sysmin_0%", "%syssec%", "%syssec_0%", "%sysday%", "%sysday_0%", "%sysmonth%",
- "%sysmonth_0%", "%sysyear%", "%sysyear_0%", "%sysyears%", "%sysweekday%", "%sysweekday_s%", "%unixtime%", "%uptime%", "%uptime_ms%",
+ "%sysmonth_0%", "%systzoffset%", "%systzoffset_s%", "%sysyear%", "%sysyear_0%", "%sysyears%", "%sysweekday%", "%sysweekday_s%", "%unixtime%", "%unixtime_lcl%", "%uptime%", "%uptime_ms%",
"%rssi%", "%ip%", "%unit%", "%unit_0%", "%ssid%", "%bssid%", "%wi_ch%", "%iswifi%", "%vcc%", "%mac%", "%mac_int%", "%isntp%", "%ismqtt%",
"%dns%", "%dns1%", "%dns2%", "%flash_freq%", "%flash_size%", "%flash_chip_vendor%", "%flash_chip_model%", "%fs_free%", "%fs_size%",
"%cpu_id%", "%cpu_freq%", "%cpu_model%", "%cpu_rev%", "%cpu_cores%", "%board_name%", "%inttemp%", "%islimited_build%", "%isvar_double%",
//String Functions
- "substring", "indexOf", "indexOf_ci", "equals", "equals_ci", "strtol", "timeToMin", "timeToSec",
+ "substring", "lookup", "indexOf", "indexOf_ci", "equals", "equals_ci", "strtol", "timeToMin", "timeToSec",
//Ethernet
"%ethwifimode%", "%ethconnected%", "%ethduplex%", "%ethspeed%", "%ethstate%", "%ethspeedstate%",
//Standard Conversions
- "%c_w_dir%", "%c_c2f%", "%c_ms2Bft%", "%c_dew_th%", "%c_alt_pres_sea%", "%c_sea_pres_alt%", "%c_cm2imp%", "%c_mm2imp%",
- "%c_m2day%", "%c_m2dh%", "%c_m2dhm%", "%c_s2dhms%", "%c_random%", "%c_2hex%", "%c_u2ip%", "%c_uname%", "%c_uage%", "%c_ubuild%", "%c_ubuildstr%",
- "%c_uload%", "%c_utype%", "%c_utypestr%",
+ "%c_w_dir%", "%c_c2f%", "%c_ms2Bft%", "%c_dew_th%", "%c_alt_pres_sea%", "%c_sea_pres_alt%", "%c_cm2imp%", "%c_isnum%", "%c_mm2imp%",
+ "%c_m2day%", "%c_m2dh%", "%c_m2dhm%", "%c_s2dhms%", "%c_ts2date%", "%c_ts2wday%", "%c_random%", "%c_2hex%", "%c_u2ip%", "%c_uname%", "%c_uage%", "%c_ubuild%", "%c_ubuildstr%",
+ "%c_uload%", "%c_utype%", "%c_utypestr%", "%c_strf%",
//Variables
- "var", "int"
+ "var", "int", "str", "length"
];
//merging displayspecific commands of P095,P096,P116,P131 into commonPlugins
diff --git a/static/espeasy.min.js b/static/espeasy.min.js
index 1e75ce7b4..c8b03135c 100644
--- a/static/espeasy.min.js
+++ b/static/espeasy.min.js
@@ -1 +1 @@
-var commonAtoms=["And","Or"],commonKeywords=["If","Else","Elseif","Endif"],commonCommands=["AccessInfo","Background","Build","ClearAccessBlock","ClearRTCam","Config","ControllerDisable","ControllerEnable","DateTime","Debug","Dec","DeepSleep","DisablePriorityTask","DNS","DST","EraseSDKWiFi","ExecuteRules","FactoryReset","Gateway","I2Cscanner","Inc","IP","Let","Load","LogEntry","LogPortStatus","LoopTimerSet","LoopTimerSet_ms","LoopTimerSetAndRun","LoopTimerSetAndRun_ms","MemInfo","MemInfoDetail","Name","Password","PostToHTTP","Publish","PublishR","Reboot","Save","SendTo","SendToHTTP","SendToUDP","Settings","Subnet","Subscribe","TaskClear","TaskClearAll","TaskDisable","TaskEnable","TaskRun","TaskValueSet","TaskValueSetAndRun","TimerPause","TimerResume","TimerSet","TimerSet_ms","TimeZone","UdpPort","UdpTest","Unit","UseNTP","WdConfig","WdRead","WiFi","WiFiAllowAP","WiFiAPMode","WiFiConnect","WiFiDisconnect","WiFiKey","WiFiKey2","WiFiMode","WiFiScan","WiFiSSID","WiFiSSID2","WiFiSTAMode","Event","AsyncEvent","GPIO","GPIOToggle","LongPulse","LongPulse_mS","Monitor","Pulse","PWM","Servo","Status","Tone","RTTTL","UnMonitor","Provision","Provision,Config","Provision,Security","Provision,Notification","Provision,Provision","Provision,Rules,","Provision,CustomCdnUrl","Provision,Firmware,"],commonEvents=["Clock#Time","Login#Failed","MQTT#Connected","MQTT#Disconnected","MQTTimport#Connected","MQTTimport#Disconnected","Rules#Timer","System#Boot","System#BootMode","System#Sleep","System#Wake","TaskExit#","TaskInit#","ThingspeakReply","Time#Initialized","Time#Set","WiFi#APmodeDisabled","WiFi#APmodeEnabled","WiFi#ChangedAccesspoint","WiFi#ChangedWiFichannel","WiFi#Connected","WiFi#Disconnected","ProvisionFirmware"],commonPlugins=["ResetPulseCounter","SetPulseCounterTotal","LogPulseStatistic","analogout","MCPGPIO","MCPGPIOToggle","MCPLongPulse","MCPLongPulse_ms","MCPPulse","Status,MCP","Monitor,MCP","MonitorRange,MCP","UnMonitorRange,MCP","UnMonitor,MCP","MCPGPIORange","MCPGPIOPattern","MCPMode","MCPModeRange","ExtGpio","ExtPwm","ExtPulse","ExtLongPulse","Status,EXT,","LCDCmd","LCD","PCFGPIO","PCFGPIOToggle","PCFLongPulse","PCFLongPulse_ms","PCFPulse","Status,PCF","Monitor,PCF","MonitorRange,PCF","UnMonitorRange,PCF","UnMonitor,PCF","PCFGPIORange","PCFGPIOpattern","PCFMode","PCFmodeRange","SerialSend","SerialSendMix","Ser2NetClientSend","pcapwm","pcafrq","mode2","OLED","OLEDCMD","OLEDCMD,on","OLEDCMD,off","OLEDCMD,clear","IRSEND","IRSENDAC","OledFramedCmd","OledFramedCmd,Display","OledFramedCmd,low","OledFramedCmd,med","OledFramedCmd,high","OledFramedCmd,Frame","OledFramedCmd,linecount","OledFramedCmd,leftalign","OledFramedCmd,align","OledFramedCmd,userDef1","OledFramedCmd,userDef2","NeoPixel","NeoPixelAll","NeoPixelLine","NeoPixelHSV","NeoPixelAllHSV","NeoPixelLineHSV","NeoPixelBright","MotorShieldCmd,DCMotor","MotorShieldCmd,Stepper","MHZCalibrateZero","MHZReset","MHZABCEnable","MHZABCDisable","Sensair_SetRelay","PMSX003","PMSX003,Wake","PMSX003,Sleep","PMSX003,Reset","encwrite","Play","Vol","Eq","Mode","Repeat","tareChanA","tareChanB","7dn","7dst","7dsd","7dtext","7ddt","7dt","7dtfont","7dtbin","7don","7doff","7output","HLWCalibrate","HLWReset","csecalibrate","cseclearpulses","csereset","WemosMotorShieldCMD","LolinMotorShieldCMD","GPS","GPS,Sleep","GPS,Wake","GPS#GotFix","GPS#LostFix","GPS#Travelled","homieValueSet","SerialProxy_Write","SerialProxy_WriteMix","SerialProxy_Test","HeatPumpir","MitsubishiHP","MitsubishiHP,temperature","MitsubishiHP,power","MitsubishiHP,mode","MitsubishiHP,fan","MitsubishiHP,vane","MitsubishiHP,widevane","Culreader_Write","Touch","Touch,Rot","Touch,Flip","Touch,Enable","Touch,Disable","Touch,On","Touch,Off","Touch,Toggle","Touch,Setgrp","Touch,Incgrp","Touch,Decgrp","Touch,Incpage","Touch,Decpage","Touch,Updatebutton","WakeOnLan","DotMatrix","DotMatrix,clear","DotMatrix,update","DotMatrix,size","DotMatrix,txt","DotMatrix,settxt","DotMatrix,content","DotMatrix,alignment","DotMatrix,anim.in","DotMatrix,anim.out","DotMatrix,speed","DotMatrix,pause","DotMatrix,font","DotMatrix,layout","DotMatrix,inverted","DotMatrix,specialeffect","DotMatrix,offset","DotMatrix,brightness","DotMatrix,repeat","DotMatrix,setbar","DotMatrix,bar","Thermo","Thermo,Up","Thermo,Down","Thermo,Mode","Thermo,ModeBtn","Thermo,Setpoint","Max1704xclearalert","scdgetabc","scdgetalt","scdgettmp","scdsetcalibration","scdsetfrc","scdgetinterval","multirelay","multirelay,on","multirelay,off","multirelay,set","multirelay,get","multirelay,loop","ShiftOut","ShiftOut,Set","ShiftOut,SetNoUpdate","ShiftOut,Update","ShiftOut,SetAll","ShiftOut,SetAllNoUpdate","ShiftOut,SetAllLow","ShiftOut,SetAllHigh","ShiftOut,SetChipCount","ShiftOut,SetHexBin","cdmrst","nfx","nfx,off","nfx,on","nfx,dim","nfx,line,","nfx,hsvline,","nfx,one,","nfx,hsvone,","nfx,all,","nfx,rgb,","nfx,fade,","nfx,hsv,","nfx,colorfade,","nfx,rainbow","nfx,kitt,","nfx,comet,","nfx,theatre,","nfx,scan,","nfx,dualscan,","nfx,twinkle,","nfx,twinklefade,","nfx,sparkle,","nfx,wipe,","nfx,dualwipe","nfx,fire","nfx,fireflicker","nfx,faketv","nfx,simpleclock","nfx,stop","nfx,statusrequest","nfx,fadetime,","nfx,fadedelay,","nfx,speed,","nfx,count,","nfx,bgcolor","ShiftIn","ShiftIn,PinEvent","ShiftIn,ChipEvent","ShiftIn,SetChipCount","ShiftIn,SampleFrequency","ShiftIn,EventPerPin","scd4x","scd4x,storesettings","scd4x,facoryreset","scd4x,selftest","scd4x,setfrc,","axp","axp,ldo2","axp,ldo3","axp,ldoio","axp,gpio0","axp,gpio1","axp,gpio2","axp,gpio3","axp,gpio4","axp,dcdc2","axp,dcdc3","axp,ldo2map","axp,ldo3map","axp,ldoiomap","axp,dcdc2map","axp,dcdc3map","axp,ldo2perc","axp,ldo3perc","axp,ldoioperc","axp,dcdc2perc","axp,dcdc3perc","I2CEncoder","I2CEncoder,bright","I2CEncoder,led1","I2CEncoder,led2","I2CEncoder,gain","I2CEncoder,set","cachereader","cachereader,readpos","cachereader,sendtaskinfo","cachereader,flush","tm1621","tm1621,write,","tm1621,writerow,","tm1621,voltamp,","tm1621,energy,","tm1621,celcius,","tm1621,fahrenheit,","tm1621,humidity,","tm1621,raw,","dac","dac,1","dac,2","sht4x","sht4x,startup","ld2410","ld2410,factoryreset","ld2410,logall","digipot","digipot,reset","digipot,shutdown","digipot,","7dextra","7dbefore","7dgroup","7digit","7color","7digitcolor","7groupcolor","gp8403","gp8403,volt,","gp8403,mvolt,","gp8403,range,","gp8403,preset,","gp8403,init,","sen5x","sen5x,startclean","sen5x,techlog,","as3935","as3935,clearstats","as3935,calibrate","as3935,setgain,","as3935,setnf,","as3935,setwd,","as3925,setsrej,"],pluginDispKind=["tft","ili9341","ili9342","ili9481","ili9486","ili9488","epd","eink","epaper","il3897","uc8151d","ssd1680","ws2in7","ws1in54","st77xx","st7735","st7789","st7796","neomatrix","neo","pcd8544"],pluginDispCmd=["cmd,on","cmd,off","cmd,clear","cmd,backlight","cmd,bright","cmd,deepsleep","cmd,seq_start","cmd,seq_end","cmd,inv","cmd,rot",",clear",",rot",",tpm",",txt",",txp",",txz",",txc",",txs",",txtfull",",asciitable",",font",",l",",lh",",lv",",lm",",lmr",",r",",rf",",c",",cf",",rf",",t",",tf",",rr",",rrf",",px",",pxh",",pxv",",bmp",",btn",",win",",defwin",",delwin"],commonTag=["On","Do","Endon"],commonNumber=["toBin","toHex","Constrain","XOR","AND:","OR:","Ord","bitRead","bitSet","bitClear","bitWrite","urlencode"],commonMath=["Log","Ln","Abs","Exp","Sqrt","Sq","Round","Sin","Cos","Tan","aSin","aCos","aTan","Sin_d","Cos_d","Tan_d","aSin_d","aCos_d","aTan_d","map","mapc"],commonWarning=["delay","Delay","ResetFlashWriteCounter"],taskSpecifics=["settings.Enabled","settings.Interval","settings.ValueCount","settings.Controller1.Enabled","settings.Controller2.Enabled","settings.Controller3.Enabled","settings.Controller1.Idx","settings.Controller2.Idx","settings.Controller3.Idx"],AnythingElse=["%eventvalue%","%eventpar%","%eventname%","%sysname%","%bootcause%","%systime%","%systm_hm%","%systm_hm_0%","%systm_hm_sp%","%systime_am%","%systime_am_0%","%systime_am_sp%","%systm_hm_am%","%systm_hm_am_0%","%systm_hm_am_sp%","%lcltime%","%sunrise%","%s_sunrise%","%m_sunrise%","%sunset%","%s_sunset%","%m_sunset%","%lcltime_am%","%syshour%","%syshour_0%","%sysmin%","%sysmin_0%","%syssec%","%syssec_0%","%sysday%","%sysday_0%","%sysmonth%","%sysmonth_0%","%sysyear%","%sysyear_0%","%sysyears%","%sysweekday%","%sysweekday_s%","%unixtime%","%uptime%","%uptime_ms%","%rssi%","%ip%","%unit%","%unit_0%","%ssid%","%bssid%","%wi_ch%","%iswifi%","%vcc%","%mac%","%mac_int%","%isntp%","%ismqtt%","%dns%","%dns1%","%dns2%","%flash_freq%","%flash_size%","%flash_chip_vendor%","%flash_chip_model%","%fs_free%","%fs_size%","%cpu_id%","%cpu_freq%","%cpu_model%","%cpu_rev%","%cpu_cores%","%board_name%","%inttemp%","%islimited_build%","%isvar_double%","substring","indexOf","indexOf_ci","equals","equals_ci","strtol","timeToMin","timeToSec","%ethwifimode%","%ethconnected%","%ethduplex%","%ethspeed%","%ethstate%","%ethspeedstate%","%c_w_dir%","%c_c2f%","%c_ms2Bft%","%c_dew_th%","%c_alt_pres_sea%","%c_sea_pres_alt%","%c_cm2imp%","%c_mm2imp%","%c_m2day%","%c_m2dh%","%c_m2dhm%","%c_s2dhms%","%c_random%","%c_2hex%","%c_u2ip%","%c_uname%","%c_uage%","%c_ubuild%","%c_ubuildstr%","%c_uload%","%c_utype%","%c_utypestr%","var","int"];for(const e of pluginDispKind)commonPlugins=commonPlugins.concat(e);for(const e of pluginDispKind)for(const t of pluginDispCmd){let n=e+t;commonPlugins=commonPlugins.concat(n)}var rEdit,EXTRAWORDS=commonAtoms.concat(commonPlugins,commonKeywords,commonCommands,commonEvents,commonTag,commonNumber,commonMath,commonWarning,taskSpecifics,AnythingElse);function initCM(){CodeMirror.commands.autocomplete=function(e){e.showHint({hint:CodeMirror.hint.anyword})},(rEdit=CodeMirror.fromTextArea(document.getElementById("rules"),{tabSize:2,indentWithTabs:!1,lineNumbers:!0,autoCloseBrackets:!0,extraKeys:{"Ctrl-Space":"autocomplete",Tab:e=>{"null"===e.getMode().name?e.execCommand("insertTab"):e.somethingSelected()?e.execCommand("indentMore"):e.execCommand("insertSoftTab")},"Shift-Tab":e=>e.execCommand("indentLess")}})).on("change",(function(){rEdit.save()})),rEdit.on("inputRead",(function(e,t){var n=e.getCursor(),o=e.getTokenAt(n);/[\w%,.]/.test(t.text)&&"comment"!=o.type&&e.showHint({completeSingle:!1})}))}!function(e){"object"==typeof exports&&"object"==typeof module?e(require("codemirror")):"function"==typeof define&&define.amd?define(["codemirror"],e):e(CodeMirror)}((function(e){"use strict";e.defineMode("espeasy",(function(){var e={};function t(t,n){for(var o=0;oe.toLowerCase()));commonCommands=commonCommands.concat(n);var o=commonEvents.map((e=>e.toLowerCase()));commonEvents=commonEvents.concat(o);var i=commonPlugins.map((e=>e.toLowerCase()));commonPlugins=commonPlugins.concat(i);var r=commonAtoms.map((e=>e.toLowerCase()));commonAtoms=commonAtoms.concat(r);var a=commonKeywords.map((e=>e.toLowerCase()));commonKeywords=commonKeywords.concat(a);var s=commonTag.map((e=>e.toLowerCase()));commonTag=commonTag.concat(s);var c=commonNumber.map((e=>e.toLowerCase()));commonNumber=commonNumber.concat(c);var l=commonMath.map((e=>e.toLowerCase()));commonMath=commonMath.concat(l);var m=AnythingElse.map((e=>e.toLowerCase()));AnythingElse=AnythingElse.concat(m);var d=taskSpecifics.map((e=>e.toLowerCase()));function u(t,n){if(t.eatSpace())return null;t.sol();var o=t.next();if(/\d/.test(o)){if("0"==o)return"x"===t.next()?(t.eatWhile(/\w/),"number"):(t.eatWhile(/\d|\./),"number");if(t.eatWhile(/\d|\./),!t.match("d")&&!t.match("output")&&(t.eol()||/\D/.test(t.peek())))return"number"}if(/\w/.test(o))for(const e of EXTRAWORDS){let n=e.substring(1);(e.includes(":")||e.includes(",")||e.includes("."))&&t.match(n)}if(/\w/.test(o)&&(t.eatWhile(/[\w]/),t.match(".gpio")||t.match(".pulse")||t.match(".frq")||t.match(".pwm")))return"def";if("\\"===o)return t.next(),null;if("("===o||")"===o)return"bracket";if("{"===o||"}"===o||":"===o)return"number";if("/"==o)return/\//.test(t.peek())?(t.skipToEnd(),"comment"):"operator";if("'"==o&&(t.eatWhile(/[^']/),t.match("'")))return"attribute";if("+"===o||"="===o||"<"===o||">"===o||"-"===o||","===o||"*"===o||"!"===o)return"operator";if("%"==o){if(/\d/.test(t.next()))return"number";if(t.eatWhile(/[^\s\%]/),t.match("%"))return"hr"}if("["==o&&(t.eatWhile(/[^\s\]]/),t.eat("]")))return"hr";t.eatWhile(/\w/);var i=t.current();return/\w/.test(o)&&t.match("#")?(t.eatWhile(/[\w.#]/),"events"):"#"===o?(t.eatWhile(/\w/),"number"):e.hasOwnProperty(i)?e[i]:null}function f(e,t){return(t.tokens[0]||u)(e,t)}return taskSpecifics=taskSpecifics.concat(d),t("atom",commonAtoms),t("keyword",commonKeywords),t("builtin",commonCommands),t("events",commonEvents),t("def",commonPlugins),t("tag",commonTag),t("number",commonNumber),t("bracket",commonMath),t("warning",commonWarning),t("hr",AnythingElse),t("comment",taskSpecifics),{startState:function(){return{tokens:[]}},token:function(e,t){return f(e,t)},closeBrackets:"[]{}''\"\"``()",lineComment:"//",fold:"brace"}}))})),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],mod):e(CodeMirror)}((function(e){var t={pairs:"()[]{}''\"\"",closeBefore:")]}'\":;>",triples:"",explode:"[]{}"},n=e.Pos;function o(e,n){return"pairs"==n&&"string"==typeof e?e:"object"==typeof e&&null!=e[n]?e[n]:t[n]}e.defineOption("autoCloseBrackets",!1,(function(t,n,a){a&&a!=e.Init&&(t.removeKeyMap(i),t.state.closeBrackets=null),n&&(r(o(n,"pairs")),t.state.closeBrackets=n,t.addKeyMap(i))}));var i={Backspace:function(t){var i=s(t);if(!i||t.getOption("disableInput"))return e.Pass;for(var r=o(i,"pairs"),a=t.listSelections(),c=0;c=0;c--){var d=a[c].head;t.replaceRange("",n(d.line,d.ch-1),n(d.line,d.ch+1),"+delete")}},Enter:function(t){var n=s(t),i=n&&o(n,"explode");if(!i||t.getOption("disableInput"))return e.Pass;for(var r=t.listSelections(),a=0;a1&&p.indexOf(i)>=0&&t.getRange(n(M.line,M.ch-2),M)==i+i){if(M.ch>2&&/\bstring/.test(t.getTokenTypeAt(n(M.line,M.ch-2))))return e.Pass;S="addFour"}else if(h){var T=0==M.ch?" ":t.getRange(n(M.line,M.ch-1),M);if(e.isWordChar(b)||T==i||e.isWordChar(T))return e.Pass;S="both"}else{if(!x||!(0===b.length||/\s/.test(b)||f.indexOf(b)>-1))return e.Pass;S="both"}else S=h&&d(t,M)?"both":p.indexOf(i)>=0&&t.getRange(M,n(M.line,M.ch+3))==i+i+i?"skipThree":"skip";if(u){if(u!=S)return e.Pass}else u=S}var y=m%2?a.charAt(m-1):i,v=m%2?i:a.charAt(m+1);t.operation((function(){if("skip"==u)c(t,1);else if("skipThree"==u)c(t,3);else if("surround"==u){for(var e=t.getSelections(),n=0;n0?{line:a.head.line,ch:a.head.ch+t}:{line:a.head.line-1};n.push({anchor:s,head:s})}e.setSelections(n,i)}function l(t){var o=e.cmpPos(t.anchor,t.head)>0;return{anchor:new n(t.anchor.line,t.anchor.ch+(o?-1:1)),head:new n(t.head.line,t.head.ch+(o?1:-1))}}function m(e,t){var o=e.getRange(n(t.line,t.ch-1),n(t.line,t.ch+1));return 2==o.length?o:null}function d(e,t){var o=e.getTokenAt(n(t.line,t.ch+1));return/\bstring/.test(o.type)&&o.start==t.ch&&(0==t.ch||!/\bstring/.test(e.getTokenTypeAt(t)))}r(t.pairs+"`")}));
\ No newline at end of file
+var rEdit,commonAtoms=["And","Or"],commonKeywords=["If","Else","Elseif","Endif"],commonCommands=["AccessInfo","Background","Build","ClearAccessBlock","ClearRTCam","Config","ControllerDisable","ControllerEnable","DateTime","Debug","Dec","DeepSleep","DisablePriorityTask","DNS","DST","EraseSDKWiFi","ExecuteRules","FactoryReset","Gateway","I2Cscanner","Inc","IP","Let","LetStr","Load","LogEntry","LogPortStatus","LoopTimerSet","LoopTimerSet_ms","LoopTimerSetAndRun","LoopTimerSetAndRun_ms","MemInfo","MemInfoDetail","Name","Password","PostToHTTP","Publish","PublishR","Reboot","Save","SendTo","SendToHTTP","SendToUDP","SendToUDPMix","Settings","Subnet","Subscribe","TaskClear","TaskClearAll","TaskDisable","TaskEnable","TaskRun","TaskValueSet","TaskValueSetAndRun","TaskValueSetDerived","TaskValueSetPresentation","TimerPause","TimerResume","TimerSet","TimerSet_ms","TimeZone","UdpPort","UdpTest","Unit","UseNTP","WdConfig","WdRead","WiFi","WiFiAllowAP","WiFiAPMode","WiFiConnect","WiFiDisconnect","WiFiKey","WiFiKey2","WiFiMode","WiFiScan","WiFiSSID","WiFiSSID2","WiFiSTAMode","Event","AsyncEvent","GPIO","GPIOToggle","LongPulse","LongPulse_mS","Monitor","Pulse","PWM","Servo","Status","Tone","RTTTL","UnMonitor","Provision","Provision,Config","Provision,Security","Provision,Notification","Provision,Provision","Provision,Rules,","Provision,CustomCdnUrl","Provision,Firmware,",],commonEvents=["Clock#Time","Login#Failed","MQTT#Connected","MQTT#Disconnected","MQTTimport#Connected","MQTTimport#Disconnected","Rules#Timer","System#Boot","System#BootMode","System#Sleep","System#Wake","TaskExit#","TaskInit#","ThingspeakReply","Time#Initialized","Time#Set","WiFi#APmodeDisabled","WiFi#APmodeEnabled","WiFi#ChangedAccesspoint","WiFi#ChangedWiFichannel","WiFi#Connected","WiFi#Disconnected","ProvisionFirmware",],commonPlugins=["ResetPulseCounter","SetPulseCounterTotal","LogPulseStatistic","analogout","MCPGPIO","MCPGPIOToggle","MCPLongPulse","MCPLongPulse_ms","MCPPulse","Status,MCP","Monitor,MCP","MonitorRange,MCP","UnMonitorRange,MCP","UnMonitor,MCP","MCPGPIORange","MCPGPIOPattern","MCPMode","MCPModeRange","ExtGpio","ExtPwm","ExtPulse","ExtLongPulse","Status,EXT,","LCDCmd","LCD","PCFGPIO","PCFGPIOToggle","PCFLongPulse","PCFLongPulse_ms","PCFPulse","Status,PCF","Monitor,PCF","MonitorRange,PCF","UnMonitorRange,PCF","UnMonitor,PCF","PCFGPIORange","PCFGPIOpattern","PCFMode","PCFmodeRange","SerialSend","SerialSendMix","Ser2NetClientSend","SerialSend_test","pcapwm","pcafrq","mode2","OLED","OLEDCMD","OLEDCMD,on","OLEDCMD,off","OLEDCMD,clear","IRSEND","IRSENDAC","OledFramedCmd","OledFramedCmd,Display","OledFramedCmd,low","OledFramedCmd,med","OledFramedCmd,high","OledFramedCmd,Frame","OledFramedCmd,linecount","OledFramedCmd,leftalign","OledFramedCmd,align","OledFramedCmd,userDef1","OledFramedCmd,userDef2","NeoPixel","NeoPixelAll","NeoPixelLine","NeoPixelHSV","NeoPixelAllHSV","NeoPixelLineHSV","NeoPixelBright","MotorShieldCmd,DCMotor","MotorShieldCmd,Stepper","MHZCalibrateZero","MHZReset","MHZABCEnable","MHZABCDisable","Sensair_SetRelay","PMSX003","PMSX003,Wake","PMSX003,Sleep","PMSX003,Reset","encwrite","Play","Vol","Eq","Mode","Repeat","tareChanA","tareChanB","7dn","7dst","7dsd","7dtext","7ddt","7dt","7dtfont","7dtbin","7don","7doff","7output","HLWCalibrate","HLWReset","csecalibrate","cseclearpulses","csereset","WemosMotorShieldCMD","LolinMotorShieldCMD","GPS","GPS,Sleep","GPS,Wake","GPS#GotFix","GPS#LostFix","GPS#Travelled","homieValueSet","SerialProxy_Write","SerialProxy_WriteMix","SerialProxy_Test","HeatPumpir","MitsubishiHP","MitsubishiHP,temperature","MitsubishiHP,power","MitsubishiHP,mode","MitsubishiHP,fan","MitsubishiHP,vane","MitsubishiHP,widevane","Culreader_Write","Touch","Touch,Rot","Touch,Flip","Touch,Enable","Touch,Disable","Touch,On","Touch,Off","Touch,Toggle","Touch,Setgrp","Touch,Incgrp","Touch,Decgrp","Touch,Incpage","Touch,Decpage","Touch,Updatebutton","WakeOnLan","DotMatrix","DotMatrix,clear","DotMatrix,update","DotMatrix,size","DotMatrix,txt","DotMatrix,settxt","DotMatrix,content","DotMatrix,alignment","DotMatrix,anim.in","DotMatrix,anim.out","DotMatrix,speed","DotMatrix,pause","DotMatrix,font","DotMatrix,layout","DotMatrix,inverted","DotMatrix,specialeffect","DotMatrix,offset","DotMatrix,brightness","DotMatrix,repeat","DotMatrix,setbar","DotMatrix,bar","Thermo","Thermo,Up","Thermo,Down","Thermo,Mode","Thermo,ModeBtn","Thermo,Setpoint","Max1704xclearalert","scdgetabc","scdgetalt","scdgettmp","scdsetcalibration","scdsetfrc","scdgetinterval","multirelay","multirelay,on","multirelay,off","multirelay,set","multirelay,get","multirelay,loop","ShiftOut","ShiftOut,Set","ShiftOut,SetNoUpdate","ShiftOut,Update","ShiftOut,SetAll","ShiftOut,SetAllNoUpdate","ShiftOut,SetAllLow","ShiftOut,SetAllHigh","ShiftOut,SetChipCount","ShiftOut,SetHexBin","cdmrst","nfx","nfx,off","nfx,on","nfx,dim","nfx,line,","nfx,hsvline,","nfx,one,","nfx,hsvone,","nfx,all,","nfx,rgb,","nfx,fade,","nfx,hsv,","nfx,colorfade,","nfx,rainbow","nfx,kitt,","nfx,comet,","nfx,theatre,","nfx,scan,","nfx,dualscan,","nfx,twinkle,","nfx,twinklefade,","nfx,sparkle,","nfx,wipe,","nfx,dualwipe","nfx,fire","nfx,fireflicker","nfx,faketv","nfx,simpleclock","nfx,stop","nfx,statusrequest","nfx,fadetime,","nfx,fadedelay,","nfx,speed,","nfx,count,","nfx,bgcolor","ShiftIn","ShiftIn,PinEvent","ShiftIn,ChipEvent","ShiftIn,SetChipCount","ShiftIn,SampleFrequency","ShiftIn,EventPerPin","scd4x","scd4x,storesettings","scd4x,facoryreset","scd4x,selftest","scd4x,setfrc,","axp","axp,ldo2","axp,ldo3","axp,ldoio","axp,gpio0","axp,gpio1","axp,gpio2","axp,gpio3","axp,gpio4","axp,dcdc2","axp,dcdc3","axp,ldo2map","axp,ldo3map","axp,ldoiomap","axp,dcdc2map","axp,dcdc3map","axp,ldo2perc","axp,ldo3perc","axp,ldoioperc","axp,dcdc2perc","axp,dcdc3perc","I2CEncoder","I2CEncoder,bright","I2CEncoder,led1","I2CEncoder,led2","I2CEncoder,gain","I2CEncoder,set","cachereader","cachereader,readpos","cachereader,sendtaskinfo","cachereader,flush","tm1621","tm1621,write,","tm1621,writerow,","tm1621,voltamp,","tm1621,energy,","tm1621,celcius,","tm1621,fahrenheit,","tm1621,humidity,","tm1621,raw,","dac","dac,1","dac,2","sht4x","sht4x,startup","ld2410","ld2410,factoryreset","ld2410,logall","digipot","digipot,reset","digipot,shutdown","digipot,","7dextra","7dbefore","7dgroup","7digit","7color","7digitcolor","7groupcolor","gp8403","gp8403,volt,","gp8403,mvolt,","gp8403,range,","gp8403,preset,","gp8403,init,","sen5x","sen5x,startclean","sen5x,techlog,","as3935","as3935,clearstats","as3935,calibrate","as3935,setgain,","as3935,setnf,","as3935,setwd,","as3925,setsrej,","lu9685","lu9685,servo,","lu9685,enable,","lu9685,disable,","lu9685,setrange,","geni2c","geni2c,cmd,","geni2c,exec,","geni2c,log,",],pluginDispKind=["tft","ili9341","ili9342","ili9481","ili9486","ili9488","epd","eink","epaper","il3897","uc8151d","ssd1680","ws2in7","ws1in54","st77xx","st7735","st7789","st7796","neomatrix","neo","pcd8544",],pluginDispCmd=["cmd,on","cmd,off","cmd,clear","cmd,backlight","cmd,bright","cmd,deepsleep","cmd,seq_start","cmd,seq_end","cmd,inv","cmd,rot",",clear",",rot",",tpm",",txt",",txp",",txz",",txc",",txs",",txtfull",",asciitable",",font",",l",",lh",",lv",",lm",",lmr",",r",",rf",",c",",cf",",rf",",t",",tf",",rr",",rrf",",px",",pxh",",pxv",",bmp",",btn",",win",",defwin",",delwin",],commonTag=["On","Do","Endon"],commonNumber=["toBin","toHex","Constrain","XOR","AND:","OR:","Ord","bitRead","bitSet","bitClear","bitWrite","urlencode"],commonMath=["Log","Ln","Abs","Exp","Sqrt","Sq","Round","Sin","Cos","Tan","aSin","aCos","aTan","Sin_d","Cos_d","Tan_d","aSin_d","aCos_d","aTan_d","map","mapc"],commonWarning=["delay","Delay","ResetFlashWriteCounter"],taskSpecifics=["settings.Enabled","settings.Interval","settings.ValueCount","settings.Controller1.Enabled","settings.Controller2.Enabled","settings.Controller3.Enabled","settings.Controller1.Idx","settings.Controller2.Idx","settings.Controller3.Idx"],AnythingElse=["%eventvalue%","%eventpar%","%eventname%","%sysname%","%bootcause%","%systime%","%systm_hm%","%systm_hm_0%","%systm_hm_sp%","%systime_am%","%systime_am_0%","%systime_am_sp%","%systm_hm_am%","%systm_hm_am_0%","%systm_hm_am_sp%","%lcltime%","%sunrise%","%s_sunrise%","%m_sunrise%","%sunset%","%s_sunset%","%m_sunset%","%lcltime_am%","%syshour%","%syshour_0%","%sysmin%","%sysmin_0%","%syssec%","%syssec_0%","%sysday%","%sysday_0%","%sysmonth%","%sysmonth_0%","%systzoffset%","%systzoffset_s%","%sysyear%","%sysyear_0%","%sysyears%","%sysweekday%","%sysweekday_s%","%unixtime%","%unixtime_lcl%","%uptime%","%uptime_ms%","%rssi%","%ip%","%unit%","%unit_0%","%ssid%","%bssid%","%wi_ch%","%iswifi%","%vcc%","%mac%","%mac_int%","%isntp%","%ismqtt%","%dns%","%dns1%","%dns2%","%flash_freq%","%flash_size%","%flash_chip_vendor%","%flash_chip_model%","%fs_free%","%fs_size%","%cpu_id%","%cpu_freq%","%cpu_model%","%cpu_rev%","%cpu_cores%","%board_name%","%inttemp%","%islimited_build%","%isvar_double%","substring","lookup","indexOf","indexOf_ci","equals","equals_ci","strtol","timeToMin","timeToSec","%ethwifimode%","%ethconnected%","%ethduplex%","%ethspeed%","%ethstate%","%ethspeedstate%","%c_w_dir%","%c_c2f%","%c_ms2Bft%","%c_dew_th%","%c_alt_pres_sea%","%c_sea_pres_alt%","%c_cm2imp%","%c_isnum%","%c_mm2imp%","%c_m2day%","%c_m2dh%","%c_m2dhm%","%c_s2dhms%","%c_ts2date%","%c_ts2wday%","%c_random%","%c_2hex%","%c_u2ip%","%c_uname%","%c_uage%","%c_ubuild%","%c_ubuildstr%","%c_uload%","%c_utype%","%c_utypestr%","%c_strf%","var","int","str","length"];for(const element2 of pluginDispKind)commonPlugins=commonPlugins.concat(element2);for(const element2 of pluginDispKind)for(const element3 of pluginDispCmd){let e=element2+element3;commonPlugins=commonPlugins.concat(e)}var EXTRAWORDS=commonAtoms.concat(commonPlugins,commonKeywords,commonCommands,commonEvents,commonTag,commonNumber,commonMath,commonWarning,taskSpecifics,AnythingElse);function initCM(){CodeMirror.commands.autocomplete=function(e){e.showHint({hint:CodeMirror.hint.anyword})},(rEdit=CodeMirror.fromTextArea(document.getElementById("rules"),{tabSize:2,indentWithTabs:!1,lineNumbers:!0,autoCloseBrackets:!0,extraKeys:{"Ctrl-Space":"autocomplete",Tab(e){"null"===e.getMode().name?e.execCommand("insertTab"):e.somethingSelected()?e.execCommand("indentMore"):e.execCommand("insertSoftTab")},"Shift-Tab":e=>e.execCommand("indentLess")}})).on("change",function(){rEdit.save()}),rEdit.on("inputRead",function(e,t){var n=e.getCursor(),o=e.getTokenAt(n);/[\w%,.]/.test(t.text)&&"comment"!=o.type&&e.showHint({completeSingle:!1})})}!function(e){"object"==typeof exports&&"object"==typeof module?e(require("codemirror")):"function"==typeof define&&define.amd?define(["codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.defineMode("espeasy",function(){var e={};function t(t,n){for(var o=0;oe.toLowerCase());commonCommands=commonCommands.concat(n);var o=commonEvents.map(e=>e.toLowerCase());commonEvents=commonEvents.concat(o);var i=commonPlugins.map(e=>e.toLowerCase());commonPlugins=commonPlugins.concat(i);var a=commonAtoms.map(e=>e.toLowerCase());commonAtoms=commonAtoms.concat(a);var s=commonKeywords.map(e=>e.toLowerCase());commonKeywords=commonKeywords.concat(s);var r=commonTag.map(e=>e.toLowerCase());commonTag=commonTag.concat(r);var c=commonNumber.map(e=>e.toLowerCase());commonNumber=commonNumber.concat(c);var l=commonMath.map(e=>e.toLowerCase());commonMath=commonMath.concat(l);var m=AnythingElse.map(e=>e.toLowerCase());AnythingElse=AnythingElse.concat(m);var d=taskSpecifics.map(e=>e.toLowerCase());function u(t,n){if(t.eatSpace())return null;t.sol();var o=t.next();if(/\d/.test(o)){if("0"==o)return"x"===t.next()?(t.eatWhile(/\w/),"number"):(t.eatWhile(/\d|\./),"number");if(t.eatWhile(/\d|\./),!t.match("d")&&!t.match("output")&&(t.eol()||/\D/.test(t.peek())))return"number"}if(/\w/.test(o))for(let i of EXTRAWORDS){let a=i.substring(1);(i.includes(":")||i.includes(",")||i.includes("."))&&t.match(a)}if(/\w/.test(o)&&(t.eatWhile(/[\w]/),t.match(".gpio")||t.match(".pulse")||t.match(".frq")||t.match(".pwm")))return"def";if("\\"===o)return t.next(),null;if("("===o||")"===o)return"bracket";if("{"===o||"}"===o||":"===o)return"number";if("/"==o)return/\//.test(t.peek())?(t.skipToEnd(),"comment"):"operator";if("'"==o&&(t.eatWhile(/[^']/),t.match("'")))return"attribute";if("+"===o||"="===o||"<"===o||">"===o||"-"===o||","===o||"*"===o||"!"===o)return"operator";if("%"==o){if(/\d/.test(t.next()))return"number";if(t.eatWhile(/[^\s\%]/),t.match("%"))return"hr"}if("["==o&&(t.eatWhile(/[^\s\]]/),t.eat("]")))return"hr";t.eatWhile(/\w/);var s=t.current();return/\w/.test(o)&&t.match("#")?(t.eatWhile(/[\w.#]/),"events"):"#"===o?(t.eatWhile(/\w/),"number"):e.hasOwnProperty(s)?e[s]:null}return taskSpecifics=taskSpecifics.concat(d),t("atom",commonAtoms),t("keyword",commonKeywords),t("builtin",commonCommands),t("events",commonEvents),t("def",commonPlugins),t("tag",commonTag),t("number",commonNumber),t("bracket",commonMath),t("warning",commonWarning),t("hr",AnythingElse),t("comment",taskSpecifics),{startState:function(){return{tokens:[]}},token:function(e,t){var n,o;return n=e,((o=t).tokens[0]||u)(n,o)},closeBrackets:"[]{}''\"\"``()",lineComment:"//",fold:"brace"}})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],mod):e(CodeMirror)}(function(e){var t={pairs:"()[]{}''\"\"",closeBefore:")]}'\":;>",triples:"",explode:"[]{}"},n=e.Pos;function o(e,n){return"pairs"==n&&"string"==typeof e?e:"object"==typeof e&&null!=e[n]?e[n]:t[n]}e.defineOption("autoCloseBrackets",!1,function(t,n,s){s&&s!=e.Init&&(t.removeKeyMap(i),t.state.closeBrackets=null),n&&(a(o(n,"pairs")),t.state.closeBrackets=n,t.addKeyMap(i))});var i={Backspace:function t(i){var a=r(i);if(!a||i.getOption("disableInput"))return e.Pass;for(var s=o(a,"pairs"),c=i.listSelections(),l=0;l=0;l--){var u=c[l].head;i.replaceRange("",n(u.line,u.ch-1),n(u.line,u.ch+1),"+delete")}},Enter:function t(n){var i=r(n),a=i&&o(i,"explode");if(!a||n.getOption("disableInput"))return e.Pass;for(var s=n.listSelections(),l=0;l=0&&i.getRange(b,n(b.line,b.ch+3))==a+a+a?"skipThree":"skip";else if(h&&b.ch>1&&p.indexOf(a)>=0&&i.getRange(n(b.line,b.ch-2),b)==a+a){if(b.ch>2&&/\bstring/.test(i.getTokenTypeAt(n(b.line,b.ch-2))))return e.Pass;C="addFour"}else if(h){var M=0==b.ch?" ":i.getRange(n(b.line,b.ch-1),b);if(e.isWordChar(y)||M==a||e.isWordChar(M))return e.Pass;C="both"}else{if(!(x&&(0===y.length||/\s/.test(y)||f.indexOf(y)>-1)))return e.Pass;C="both"}if(P){if(P!=C)return e.Pass}else P=C}var v=u%2?m.charAt(u-1):a,D=u%2?a:m.charAt(u+1);i.operation(function(){if("skip"==P)c(i,1);else if("skipThree"==P)c(i,3);else if("surround"==P){for(var e=i.getSelections(),t=0;t0?{line:s.head.line,ch:s.head.ch+t}:{line:s.head.line-1};n.push({anchor:r,head:r})}e.setSelections(n,i)}function l(t){var o=e.cmpPos(t.anchor,t.head)>0;return{anchor:new n(t.anchor.line,t.anchor.ch+(o?-1:1)),head:new n(t.head.line,t.head.ch+(o?1:-1))}}function m(e,t){var o=e.getRange(n(t.line,t.ch-1),n(t.line,t.ch+1));return 2==o.length?o:null}function d(e,t){var o=e.getTokenAt(n(t.line,t.ch+1));return/\bstring/.test(o.type)&&o.start==t.ch&&(0==t.ch||!/\bstring/.test(e.getTokenTypeAt(t)))}a(t.pairs+"`")});
\ No newline at end of file
diff --git a/static/update_sensor_values_device_page.js b/static/update_sensor_values_device_page.js
index 9c7e633d8..443ff89c9 100644
--- a/static/update_sensor_values_device_page.js
+++ b/static/update_sensor_values_device_page.js
@@ -1,4 +1,7 @@
loopDeLoop(1000, 0);
+function elId(e) {
+ return document.getElementById(e);
+}
function loopDeLoop(timeForNext, activeRequests) {
var maximumRequests = 1;
@@ -13,7 +16,7 @@ function loopDeLoop(timeForNext, activeRequests) {
if (timeForNext == null) {
timeForNext = 1000;
}
- var i = setInterval(function() {
+ i = setInterval(function() {
if (check > 0) {
clearInterval(i);
return;
@@ -30,6 +33,7 @@ function loopDeLoop(timeForNext, activeRequests) {
}
response.json().then(function(data) {
timeForNext = data.TTL;
+ var showUoM = data.ShowUoM === undefined || (data.ShowUoM && data.ShowUoM !== 'false');
for (c = 0; c < data.Sensors.length; c++) {
if (data.Sensors[c].hasOwnProperty('TaskValues')) {
for (k = 0; k < data.Sensors[c].TaskValues.length; k++) {
@@ -39,19 +43,27 @@ function loopDeLoop(timeForNext, activeRequests) {
valueEntry = err.name;
} finally {
if (valueEntry !== 'TypeError') {
- tempValue = data.Sensors[c].TaskValues[k].Value;
- decimalsValue = data.Sensors[c].TaskValues[k].NrDecimals;
+ var tempValue = data.Sensors[c].TaskValues[k].Value;
+ var decimalsValue = data.Sensors[c].TaskValues[k].NrDecimals;
if (decimalsValue < 255) {
tempValue = parseFloat(tempValue).toFixed(decimalsValue);
}
+ var tempUoM = data.Sensors[c].TaskValues[k].UoM;
+ var tempPres = data.Sensors[c].TaskValues[k].Presentation;
+ if (tempPres) {
+ tempValue = tempPres;
+ } else
+ if (tempUoM && showUoM) {
+ tempValue += ' ' + tempUoM;
+ }
var valueID = 'value_' + (data.Sensors[c].TaskNumber - 1) + '_' + (data.Sensors[c].TaskValues[k].ValueNumber - 1);
var valueNameID = 'valuename_' + (data.Sensors[c].TaskNumber - 1) + '_' + (data.Sensors[c].TaskValues[k].ValueNumber - 1);
- var valueElement = document.getElementById(valueID);
- var valueNameElement = document.getElementById(valueNameID);
- if (valueElement !== null) {
+ var valueElement = elId(valueID);
+ var valueNameElement = elId(valueNameID);
+ if (valueElement) {
valueElement.innerHTML = tempValue;
}
- if (valueNameElement !== null) {
+ if (valueNameElement) {
valueNameElement.innerHTML = data.Sensors[c].TaskValues[k].Name + ':';
}
}
@@ -59,7 +71,6 @@ function loopDeLoop(timeForNext, activeRequests) {
}
}
}
- timeForNext = data.TTL;
clearInterval(i);
loopDeLoop(timeForNext, 0);
return;