diff --git a/docs/make.bat b/docs/make.bat index 0077a9fded..91e334b841 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,5 +1,7 @@ @ECHO OFF +set PYTHONUTF8=1 + pushd %~dp0 REM Command file for Sphinx documentation diff --git a/docs/source/Plugin/P187.rst b/docs/source/Plugin/P187.rst new file mode 100644 index 0000000000..4c5da7c8fa --- /dev/null +++ b/docs/source/Plugin/P187.rst @@ -0,0 +1,142 @@ +.. include:: ../Plugin/_plugin_substitutions_p18x.repl +.. _P187_page: + +|P187_typename| +================================================== + +|P187_shortinfo| + +Plugin details +-------------- + +Type: |P187_type| + +Name: |P187_name| + +Status ESP32: |P187_status| + +Status ESP8266: |P187_status_lb| + +GitHub: |P187_github|_ + +Maintainer: |P187_maintainer| + +Used libraries: |P187_usedlibraries| + +Introduction +------------ + +The Dummy Data Injector plugin simulates peripheral data output. It can be used e.g. to test IOT backend systems by providing input data for debugging. + +The plugin can be applied multiple times in the devices list. Up to four synchronized data streams can be generated per deployment of the plugin, but the outputs of multiple plugins are not synchronized across the plugin boundaries. + +Currently, there are following output configurations supported: + +* **Sinus**: produces sinus shaped output values. The data stream can be configured in Amplitude, Offset, Period and Phase. + +* **Trapezoid**: trapezoid shaped output values are created. The data stream's On-Level, Off-Level, Period, On-Time, Rise-Time and Fall-Time can be configured. + +* **Random**: generates random output values. Minimum and Maximum values can be configured. + +.. image:: P187_OutputCombined.png + :alt: Output view + + +Configuration +------------- + +.. image:: P187_DeviceConfiguration.png + :alt: Device configuration + +* **Name**: Required by ESPEasy, must be unique within the list of available devices/tasks. + +* **Enabled**: The device can be disabled or enabled. When not enabled, no output data is generated. + +Device Settings +^^^^^^^^^^^^^^^ + +Depending on the selected 'Number Output values', up to four sections are used to configure the output values (press 'Submit' after changing) : + +.. image:: P187_OutputConfiguration.png + :alt: Output configuration + +Output Sinus +~~~~~~~~~~~~ + +.. image:: P187_OutputConfigurationSinus.png + :alt: Output sinus configuration + +* **Amplitude**: Amplitude of the signal (integer value 0 to +65537) +* **Offset**: Offset from 0-Line (integer value -65537 to +65537) +* **Period**: period time in seconds (integer value 30 - 3600) +* **Phase**: phase offset in degrees (integer value 0 - 359) + +.. image:: P187_OutputSinus.png + :alt: Output sinus view + +- for floating value output, use formula to divide the output levels (e.g. for floating value output with 2 decimals use the formula '%value%/100'). + +Output Trapezoid +~~~~~~~~~~~~~~~~ + +.. image:: P187_OutputConfigurationTrapezoid.png + :alt: Output trapezoid configuration + +* **On-Level**: output level during On-Time period (integer value -65537 to +65537) +* **Off-Level**: output level during Off-Time period (integer value -65537 to +65537) +* **Period**: period time in seconds (integer value 30 - 3600) +* **On-Time**: duration of On-Time period in seconds (integer value 0 - 3600) +* **Rise-Time**: duration of linear slope from Off-Level to On-Level (integer value 0 - 3600) +* **Fall-Time**: duration of linear slope from On-Level to Off-Level (integer value 0 - 3600) + +.. image:: P187_OutputTrapezoid.png + :alt: Output trapezoid view + +- for inverted output, swap On-Level value with Off-Level value. +- Period must be greater or equal than the sum of On-Time, Rise-Time and Fall-Time. If not, some automatic adjustments are made to these values to meet this rule. +- for floating value output, use formula to divide the output levels (e.g. for floating value output with 2 decimals use the formula '%value%/100'). + +Output Random +~~~~~~~~~~~~~ + +.. image:: P187_OutputConfigurationRandom.png + :alt: Output random configuration + +* **Max-Level**: maximum output level (integer value -65537 to +65537) +* **Min-Level**: minimum output level (integer value -65537 to +65537) + +.. image:: P187_OutputRandom.png + :alt: Output random view + +- for floating value output, use formula to divide the output levels (e.g. for floating value output with 2 decimals use the formula '%value%/100'). + + +Performance +----------- + +For performance measurement, an ESP32 development board was reset to factory default settings via Wi-Fi. + +One 'System Info' device was added with a single output measuring the system load each 30s. + +Furthermore 3 Dummy Data Injector plugins were added to the devices: +- Number one generates 4 sinus outputs, interval was set to 1 second, 'Stats' box was activated for all four outputs. +- Number two generates 4 trapezoid outputs, interval was set to 1 second, 'Stats' box was activated for all four outputs. +- Number three generates 4 random outputs, interval was set to 1 second, 'Stats' box was activated for all four outputs. + +The devices page was open in one browser window, and the device was left idling for 5 minutes. Afterwards the System load shows ~29% + +In comparison, the same ESP32 system, after deleting the three Dummy Data Injector plugins showed an idle System load of ~23%. + +Change log +---------- + +.. versionadded:: 1.0 + ... + + |added| + Initial release version. + + + + + diff --git a/docs/source/Plugin/P187_DeviceConfiguration.png b/docs/source/Plugin/P187_DeviceConfiguration.png new file mode 100644 index 0000000000..b1ec850b0a Binary files /dev/null and b/docs/source/Plugin/P187_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P187_OutputCombined.png b/docs/source/Plugin/P187_OutputCombined.png new file mode 100644 index 0000000000..30847bf6d5 Binary files /dev/null and b/docs/source/Plugin/P187_OutputCombined.png differ diff --git a/docs/source/Plugin/P187_OutputConfiguration.png b/docs/source/Plugin/P187_OutputConfiguration.png new file mode 100644 index 0000000000..8b1b8e37b7 Binary files /dev/null and b/docs/source/Plugin/P187_OutputConfiguration.png differ diff --git a/docs/source/Plugin/P187_OutputConfigurationRandom.png b/docs/source/Plugin/P187_OutputConfigurationRandom.png new file mode 100644 index 0000000000..5751c20d18 Binary files /dev/null and b/docs/source/Plugin/P187_OutputConfigurationRandom.png differ diff --git a/docs/source/Plugin/P187_OutputConfigurationSinus.png b/docs/source/Plugin/P187_OutputConfigurationSinus.png new file mode 100644 index 0000000000..663408f7ad Binary files /dev/null and b/docs/source/Plugin/P187_OutputConfigurationSinus.png differ diff --git a/docs/source/Plugin/P187_OutputConfigurationTrapezoid.png b/docs/source/Plugin/P187_OutputConfigurationTrapezoid.png new file mode 100644 index 0000000000..4d783147a9 Binary files /dev/null and b/docs/source/Plugin/P187_OutputConfigurationTrapezoid.png differ diff --git a/docs/source/Plugin/P187_OutputRandom.png b/docs/source/Plugin/P187_OutputRandom.png new file mode 100644 index 0000000000..f45a482948 Binary files /dev/null and b/docs/source/Plugin/P187_OutputRandom.png differ diff --git a/docs/source/Plugin/P187_OutputSinus.png b/docs/source/Plugin/P187_OutputSinus.png new file mode 100644 index 0000000000..0e8d4aaf26 Binary files /dev/null and b/docs/source/Plugin/P187_OutputSinus.png differ diff --git a/docs/source/Plugin/P187_OutputTrapezoid.png b/docs/source/Plugin/P187_OutputTrapezoid.png new file mode 100644 index 0000000000..9b01e7ccc3 Binary files /dev/null and b/docs/source/Plugin/P187_OutputTrapezoid.png differ diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index 87f3366f5e..91c2031131 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -509,6 +509,7 @@ There are different released versions of ESP Easy: ":ref:`P177_page`","|P177_status|","|P177_status_lb|","P177" ":ref:`P178_page`","|P178_status|","|P178_status_lb|","P178" ":ref:`P180_page`","|P180_status|","|P180_status_lb|","P180" + ":ref:`P187_page`","|P187_status|","|P187_status_lb|","P187" .. include:: diff --git a/docs/source/Plugin/_plugin_categories.repl b/docs/source/Plugin/_plugin_categories.repl index 0c4fe03df9..7ff57e5633 100644 --- a/docs/source/Plugin/_plugin_categories.repl +++ b/docs/source/Plugin/_plugin_categories.repl @@ -11,7 +11,7 @@ .. |Plugin_Environment| replace:: :ref:`P004_page`, :ref:`P005_page`, :ref:`P006_page`, :ref:`P014_page`, :ref:`P024_page`, :ref:`P028_page`, :ref:`P030_page`, :ref:`P031_page`, :ref:`P032_page`, :ref:`P034_page`, :ref:`P039_page`, :ref:`P047_page`, :ref:`P051_page`, :ref:`P068_page`, :ref:`P069_page`, :ref:`P072_page`, :ref:`P103_page`, :ref:`P105_page`, :ref:`P106_page`, :ref:`P122_page`, :ref:`P150_page`, :ref:`P151_page`, :ref:`P153_page`, :ref:`P154_page`, :ref:`P163_page`, :ref:`P167_page`, :ref:`P169_page`, :ref:`P172_page`, :ref:`P173_page`, :ref:`P177_page` .. |Plugin_Extra_IO| replace:: :ref:`P011_page`, :ref:`P022_page`, :ref:`P178_page` .. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`, :ref:`P117_page`, :ref:`P127_page`, :ref:`P135_page`, :ref:`P145_page`, :ref:`P147_page`, :ref:`P164_page` -.. |Plugin_Generic| replace:: :ref:`P003_page`, :ref:`P026_page`, :ref:`P033_page`, :ref:`P037_page`, :ref:`P081_page`, :ref:`P100_page`, :ref:`P146_page`, :ref:`P180_page` +.. |Plugin_Generic| replace:: :ref:`P003_page`, :ref:`P026_page`, :ref:`P033_page`, :ref:`P037_page`, :ref:`P081_page`, :ref:`P100_page`, :ref:`P146_page`, :ref:`P180_page`, :ref:`P187_page` .. |Plugin_Gesture| replace:: :ref:`P064_page` .. |Plugin_Gyro| replace:: :ref:`P045_page`, :ref:`P119_page` .. |Plugin_Hardware| replace:: :ref:`P046_page` diff --git a/docs/source/Plugin/_plugin_sets_overview.repl b/docs/source/Plugin/_plugin_sets_overview.repl index 7294330f03..085265b000 100644 --- a/docs/source/Plugin/_plugin_sets_overview.repl +++ b/docs/source/Plugin/_plugin_sets_overview.repl @@ -60,6 +60,7 @@ Build set: :green:`NORMAL` ":ref:`P079_page`", "✓", "✓", "P079" ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -100,7 +101,6 @@ Build set: :yellow:`COLLECTION A` ":ref:`P009_page`", "✓", "✓", "P009" ":ref:`P010_page`", "✓", "✓", "P010" ":ref:`P011_page`", "✓", "✓", "P011" - ":ref:`P012_page`", "✓", "✓", "P012" ":ref:`P013_page`", "✓", "✓", "P013" ":ref:`P014_page`", "✓", "✓", "P014" ":ref:`P015_page`", "✓", "✓", "P015" @@ -110,7 +110,6 @@ Build set: :yellow:`COLLECTION A` ":ref:`P020_page`", "✓", "✓", "P020" ":ref:`P021_page`", "✓", "✓", "P021" ":ref:`P022_page`", "✓", "✓", "P022" - ":ref:`P023_page`", "✓", "✓", "P023" ":ref:`P024_page`", "✓", "✓", "P024" ":ref:`P025_page`", "✓", "✓", "P025" ":ref:`P026_page`", "✓", "✓", "P026" @@ -123,7 +122,6 @@ Build set: :yellow:`COLLECTION A` ":ref:`P034_page`", "✓", "✓", "P034" ":ref:`P036_page`", "✓", "✓", "P036" ":ref:`P037_page`", "✓", "✓", "P037" - ":ref:`P038_page`", "✓", "✓", "P038" ":ref:`P039_page`", "✓", "✓", "P039" ":ref:`P040_page`", "✓", "✓", "P040" ":ref:`P043_page`", "✓", "✓", "P043" @@ -178,6 +176,7 @@ Build set: :yellow:`COLLECTION A` ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -292,6 +291,7 @@ Build set: :yellow:`COLLECTION B` ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -337,7 +337,6 @@ Build set: :yellow:`COLLECTION C` ":ref:`P009_page`", "✓", "✓", "P009" ":ref:`P010_page`", "✓", "✓", "P010" ":ref:`P011_page`", "✓", "✓", "P011" - ":ref:`P012_page`", "✓", "✓", "P012" ":ref:`P013_page`", "✓", "✓", "P013" ":ref:`P014_page`", "✓", "✓", "P014" ":ref:`P015_page`", "✓", "✓", "P015" @@ -347,7 +346,6 @@ Build set: :yellow:`COLLECTION C` ":ref:`P020_page`", "✓", "✓", "P020" ":ref:`P021_page`", "✓", "✓", "P021" ":ref:`P022_page`", "✓", "✓", "P022" - ":ref:`P023_page`", "✓", "✓", "P023" ":ref:`P024_page`", "✓", "✓", "P024" ":ref:`P025_page`", "✓", "✓", "P025" ":ref:`P026_page`", "✓", "✓", "P026" @@ -360,7 +358,6 @@ Build set: :yellow:`COLLECTION C` ":ref:`P034_page`", "✓", "✓", "P034" ":ref:`P036_page`", "✓", "✓", "P036" ":ref:`P037_page`", "✓", "✓", "P037" - ":ref:`P038_page`", "✓", "✓", "P038" ":ref:`P039_page`", "✓", "✓", "P039" ":ref:`P040_page`", "✓", "✓", "P040" ":ref:`P043_page`", "✓", "✓", "P043" @@ -406,6 +403,7 @@ Build set: :yellow:`COLLECTION C` ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -517,6 +515,7 @@ Build set: :yellow:`COLLECTION D` ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -631,6 +630,7 @@ Build set: :yellow:`COLLECTION E` ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -744,6 +744,7 @@ Build set: :yellow:`COLLECTION F` ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P153_page`", "✓", "✓", "P153" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -789,7 +790,6 @@ Build set: :yellow:`COLLECTION G` ":ref:`P009_page`", "✓", "✓", "P009" ":ref:`P010_page`", "✓", "✓", "P010" ":ref:`P011_page`", "✓", "✓", "P011" - ":ref:`P012_page`", "✓", "✓", "P012" ":ref:`P013_page`", "✓", "✓", "P013" ":ref:`P014_page`", "✓", "✓", "P014" ":ref:`P015_page`", "✓", "✓", "P015" @@ -799,7 +799,6 @@ Build set: :yellow:`COLLECTION G` ":ref:`P020_page`", "✓", "✓", "P020" ":ref:`P021_page`", "✓", "✓", "P021" ":ref:`P022_page`", "✓", "✓", "P022" - ":ref:`P023_page`", "✓", "✓", "P023" ":ref:`P024_page`", "✓", "✓", "P024" ":ref:`P025_page`", "✓", "✓", "P025" ":ref:`P026_page`", "✓", "✓", "P026" @@ -812,7 +811,6 @@ Build set: :yellow:`COLLECTION G` ":ref:`P034_page`", "✓", "✓", "P034" ":ref:`P036_page`", "✓", "✓", "P036" ":ref:`P037_page`", "✓", "✓", "P037" - ":ref:`P038_page`", "✓", "✓", "P038" ":ref:`P039_page`", "✓", "✓", "P039" ":ref:`P040_page`", "✓", "✓", "P040" ":ref:`P043_page`", "✓", "✓", "P043" @@ -863,6 +861,7 @@ Build set: :yellow:`COLLECTION G` ":ref:`P170_page`", "✓", "✓", "P170" ":ref:`P172_page`", "✓", "✓", "P172" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -908,7 +907,6 @@ Build set: :yellow:`COLLECTION H` ":ref:`P009_page`", "✓", "✓", "P009" ":ref:`P010_page`", "✓", "✓", "P010" ":ref:`P011_page`", "✓", "✓", "P011" - ":ref:`P012_page`", "✓", "✓", "P012" ":ref:`P013_page`", "✓", "✓", "P013" ":ref:`P014_page`", "✓", "✓", "P014" ":ref:`P015_page`", "✓", "✓", "P015" @@ -918,7 +916,6 @@ Build set: :yellow:`COLLECTION H` ":ref:`P020_page`", "✓", "✓", "P020" ":ref:`P021_page`", "✓", "✓", "P021" ":ref:`P022_page`", "✓", "✓", "P022" - ":ref:`P023_page`", "✓", "✓", "P023" ":ref:`P024_page`", "✓", "✓", "P024" ":ref:`P025_page`", "✓", "✓", "P025" ":ref:`P026_page`", "✓", "✓", "P026" @@ -931,7 +928,6 @@ Build set: :yellow:`COLLECTION H` ":ref:`P034_page`", "✓", "✓", "P034" ":ref:`P036_page`", "✓", "✓", "P036" ":ref:`P037_page`", "✓", "✓", "P037" - ":ref:`P038_page`", "✓", "✓", "P038" ":ref:`P039_page`", "✓", "✓", "P039" ":ref:`P040_page`", "✓", "✓", "P040" ":ref:`P043_page`", "✓", "✓", "P043" @@ -974,6 +970,7 @@ Build set: :yellow:`COLLECTION H` ":ref:`P177_page`", "✓", "", "P177" ":ref:`P178_page`", "✓", "", "P178" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -1224,6 +1221,7 @@ Build set: :yellow:`DISPLAY A` ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -1311,6 +1309,7 @@ Build set: :yellow:`DISPLAY B` ":ref:`P148_page`", "✓", "", "P148" ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -1351,7 +1350,6 @@ Build set: :yellow:`ENERGY` ":ref:`P009_page`", "✓", "✓", "P009" ":ref:`P010_page`", "✓", "✓", "P010" ":ref:`P011_page`", "✓", "✓", "P011" - ":ref:`P012_page`", "✓", "✓", "P012" ":ref:`P013_page`", "✓", "✓", "P013" ":ref:`P014_page`", "✓", "✓", "P014" ":ref:`P015_page`", "✓", "✓", "P015" @@ -1361,7 +1359,6 @@ Build set: :yellow:`ENERGY` ":ref:`P020_page`", "✓", "✓", "P020" ":ref:`P021_page`", "✓", "✓", "P021" ":ref:`P022_page`", "✓", "✓", "P022" - ":ref:`P023_page`", "✓", "✓", "P023" ":ref:`P024_page`", "✓", "✓", "P024" ":ref:`P025_page`", "✓", "✓", "P025" ":ref:`P026_page`", "✓", "✓", "P026" @@ -1405,6 +1402,7 @@ Build set: :yellow:`ENERGY` ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P176_page`", "✓", "", "P176" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -1482,6 +1480,7 @@ Build set: :yellow:`IR` ":ref:`P079_page`", "✓", "✓", "P079" ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -1558,6 +1557,7 @@ Build set: :yellow:`IRext` ":ref:`P088_page`", "✓", "✓", "P088" ":ref:`P146_page`", "✓", "✓", "P146" ":ref:`P152_page`", "✓", "✓", "P152" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -1598,7 +1598,6 @@ Build set: :yellow:`NEOPIXEL` ":ref:`P009_page`", "✓", "✓", "P009" ":ref:`P010_page`", "✓", "✓", "P010" ":ref:`P011_page`", "✓", "✓", "P011" - ":ref:`P012_page`", "✓", "✓", "P012" ":ref:`P013_page`", "✓", "✓", "P013" ":ref:`P014_page`", "✓", "✓", "P014" ":ref:`P015_page`", "✓", "✓", "P015" @@ -1608,7 +1607,6 @@ Build set: :yellow:`NEOPIXEL` ":ref:`P020_page`", "✓", "✓", "P020" ":ref:`P021_page`", "✓", "✓", "P021" ":ref:`P022_page`", "✓", "✓", "P022" - ":ref:`P023_page`", "✓", "✓", "P023" ":ref:`P024_page`", "✓", "✓", "P024" ":ref:`P025_page`", "✓", "✓", "P025" ":ref:`P026_page`", "✓", "✓", "P026" @@ -1648,6 +1646,7 @@ Build set: :yellow:`NEOPIXEL` ":ref:`P152_page`", "✓", "✓", "P152" ":ref:`P165_page`", "✓", "✓", "P165" ":ref:`P180_page`", "✓", "✓", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "✓", "C001" ":ref:`C002_page`", "✓", "✓", "C002" ":ref:`C003_page`", "✓", "✓", "C003" @@ -1844,6 +1843,7 @@ Build set: :yellow:`MAX` ":ref:`P177_page`", "✓", "", "P177" ":ref:`P178_page`", "✓", "", "P178" ":ref:`P180_page`", "✓", "", "P180" + ":ref:`P187_page`", "✓", "", "P187" ":ref:`C001_page`", "✓", "", "C001" ":ref:`C002_page`", "✓", "", "C002" ":ref:`C003_page`", "✓", "", "C003" diff --git a/docs/source/Plugin/_plugin_substitutions_p18x.repl b/docs/source/Plugin/_plugin_substitutions_p18x.repl index 654288b7c3..9a2ab7d30f 100644 --- a/docs/source/Plugin/_plugin_substitutions_p18x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p18x.repl @@ -12,3 +12,16 @@ .. |P180_compileinfo| replace:: `.` .. |P180_usedlibraries| replace:: `.` +.. |P187_name| replace:: :cyan:`Dummy Data Injector` +.. |P187_type| replace:: :cyan:`Generic` +.. |P187_typename| replace:: :cyan:`Generic - Dummy Data Injector` +.. |P187_porttype| replace:: `.` +.. |P187_status| replace:: :yellow:`COLLECTION H` +.. |P187_status_lb| replace:: `.` +.. |P187_github| replace:: P187_Dummy_Data_Injector.ino +.. _P187_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P187_Dummy_Data_Injector.ino +.. |P187_usedby| replace:: `Dummy Data Injector` +.. |P187_shortinfo| replace:: `Dummy Data Injector` +.. |P187_maintainer| replace:: `.` +.. |P187_compileinfo| replace:: `.` +.. |P187_usedlibraries| replace:: `.` diff --git a/src/_P187_Dummy_Data_Injector.ino b/src/_P187_Dummy_Data_Injector.ino new file mode 100644 index 0000000000..4e96fe401d --- /dev/null +++ b/src/_P187_Dummy_Data_Injector.ino @@ -0,0 +1,325 @@ +#include "_Plugin_Helper.h" + +#ifdef USES_P187 + +// ################################################################################################# +// ################################ Plugin 187 Dummy Data Injector ################################ +// ################################################################################################# + +/** Changelog: + * 2026-06-21 SuksAe: moved time handling into P187_data_struct::plugin_read, replacing PLUGIN_TEN_PER_SECOND call + * 2026-06-19 SuksAe: added P187_data_struct to enable multiple instances of the plugin + * 2026-06-18 SuksAe: Initial plugin development + */ + +# include "src/PluginStructs/P187_data_struct.h" + +# define PLUGIN_187 +# define PLUGIN_ID_187 187 // plugin id +# define PLUGIN_NAME_187 "Generic - Dummy Data Injector" // "Plugin Name" is what will be dislpayed in the selection list +# define PLUGIN_187_DEBUG false // set to true for extra log info in the debug + +const __FlashStringHelper* Plugin_187_optionname(uint8_t value_nr, + bool displayString) { + const __FlashStringHelper *strings[] { + F("Output Sinus"), F("SINUS"), + F("Output Trapezoid"), F("TRAPEZOID"), + F("Output Random"), F("RANDOM"), + }; + + const size_t index = (2 * value_nr) + (displayString ? 0 : 1); + constexpr size_t nrStrings = NR_ELEMENTS(strings); + + if (index < nrStrings) { + return strings[index]; + } + return F(""); +} + +boolean Plugin_187(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + + auto& dev = Device[++deviceCount]; + dev.Number = PLUGIN_ID_187; // Plugin ID number. (PLUGIN_ID_187) + dev.Type = DEVICE_TYPE_DUMMY; // How the device is connected. e.g. DEVICE_TYPE_SINGLE => connected through 1 + // datapin + dev.VType = Sensor_VType::SENSOR_TYPE_QUAD; // Type of value the plugin will return. e.g. SENSOR_TYPE_STRING + dev.ValueCount = 4; // The number of output values of a plugin. The value should match the number of + // keys PLUGIN_VALUENAME1_187 + dev.OutputDataType = Output_Data_type_t::All; // Subset of selectable output data types (Default = no selection) + dev.FormulaOption = true; // Allow to enter a formula to convert values during read. (not possible with + // Custom enabled) + dev.SendDataOption = true; // Allow to send data to a controller. + dev.TimerOption = true; // Allow to set the "Interval" timer for the plugin. + dev.TimerOptional = true; // When taskdevice timer is not set and not optional, use default "Interval" + // delay (Settings.Delay) + dev.PluginStats = true; // Support for PluginStats to record last N task values, show charts etc. + dev.CustomVTypeVar = true; // Enable to allow the user to configure the Sensor_VType per Value that's available + // for the plugin + dev.MqttStateClass = true; + + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_187); + + success = true; + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + const int valueCount = getValueCountFromSensorType(static_cast(P187_OUTPUT_TYPE)); + + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + if (i < valueCount) { + ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_187_optionname(PCONFIG(i + P187_OUTPUT_OPTION_CONFIG_POS), false)); + } else { + ExtraTaskSettings.clearTaskDeviceValueName(i); + } + } + + success = true; + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = getValueCountFromSensorType(static_cast(P187_OUTPUT_TYPE)); + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->idx = P187_OUTPUT_TYPE_INDEX; + event->sensorType = static_cast(P187_OUTPUT_TYPE); + success = true; + break; + } + + + case PLUGIN_SET_DEFAULTS: + { + /* + Configuration and working variables are implemented as is because: + + -- excerpt from https://github.com/letscontrolit/ESPEasy/pull/5566#issuecomment-4739927575: + + PLUGIN_SET_DEFAULTS is called only once, right after the new plugin instance is added to the Devices list. + And never again after that. This is the place to set initial defaults for your plugin. + + PLUGIN_INIT is called every time the plugin is enabled, and should return true if all is OK, + or false if there is an error causing the plugin to stay disabled. + + -- excerpt end + + So in PLUGIN_SET_DEFAULTS, the persistent configuration values in flash are initialized. + This flash data is used in PLUGIN_WEBFORM_LOAD to provide data for the UI generation. The + flash data is updated in PLUGIN_WEBFORM_SAVE when new configuration values are submitted by + the user. + + Afterwards, when the plugin task instance is enabled via the UI (and output data starts + to be generated), a working copy of the configuration values is created by loading them + from flash to RAM. + Stored together with the volatile variables, this data is used for fast processing of + the signal generation code. + */ + + P187_config_struct tmp_config[VARS_PER_TASK]; + + P187_OUTPUT_TYPE = static_cast(Sensor_VType::SENSOR_TYPE_QUAD); + + memset(tmp_config, 0, sizeof(tmp_config)); + + for (int i = 0; i < VARS_PER_TASK; i++) + { + P187_OUTPUT_OPTIONx_CONFIG(i) = P187_OUTPUT_SINUS; + tmp_config[i].param0 = 100; + tmp_config[i].param2 = 60; + } + + SaveCustomTaskSettings(event->TaskIndex, (uint8_t *)&(tmp_config), sizeof(tmp_config), 0); // save configuration to flash + + success = true; + break; + } + + # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + case PLUGIN_GET_DISCOVERY_VTYPES: + { + # if FEATURE_CUSTOM_TASKVAR_VTYPE + + for (uint8_t i = 0; i < event->Par5; ++i) { + event->ParN[i] = ExtraTaskSettings.getTaskVarCustomVType(i); // Custom/User selection + } + # else // if FEATURE_CUSTOM_TASKVAR_VTYPE + event->Par1 = static_cast(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported + # endif // if FEATURE_CUSTOM_TASKVAR_VTYPE + + success = true; + break; + } + # endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE + + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + { + const uint8_t optionCount = P187_NR_OUTPUT_OPTIONS; + String options[optionCount]; + + for (uint8_t option = 0; option < optionCount; ++option) { + options[option] = Plugin_187_optionname(option, true); + } + + const int valueCount = getValueCountFromSensorType(static_cast(P187_OUTPUT_TYPE)); + + for (uint8_t i = 0; i < valueCount; ++i) { + const uint8_t pconfigIndex = i + P187_OUTPUT_OPTION_CONFIG_POS; + sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, optionCount, options); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + P187_config_struct tmp_config[VARS_PER_TASK]; + + LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&(tmp_config), sizeof(tmp_config)); // load configuration from flash + + const int valueCount = getValueCountFromSensorType(static_cast(P187_OUTPUT_TYPE)); + + for (int i = 0; i < valueCount; i++) + { + addTableSeparator(strformat(F("Output %d - %s"), i + 1, + FsP(Plugin_187_optionname(PCONFIG(i + P187_OUTPUT_OPTION_CONFIG_POS), false))), + 2, + 4); + + switch (P187_OUTPUT_OPTIONx_CONFIG(i)) + { + case P187_OUTPUT_SINUS: + addFormNumericBox(F("Amplitude"), getPluginCustomArgName(i * 8 + 0), tmp_config[i].param0, 0, 65537); + addFormNumericBox(F("Offset"), getPluginCustomArgName(i * 8 + 1), tmp_config[i].param1, -65537, 65537); + addFormNumericBox(F("Period"), getPluginCustomArgName(i * 8 + 2), tmp_config[i].param2, 30, 3600); + addUnit('s'); + addFormNumericBox(F("Phase"), getPluginCustomArgName(i * 8 + 3), tmp_config[i].param3, 0, 359); + addUnit("°"); + break; + case P187_OUTPUT_TRAPEZ: + addFormNumericBox(F("On-Level"), getPluginCustomArgName(i * 8 + 0), tmp_config[i].param0, -65537, 65537); + addFormNumericBox(F("Off-Level"), getPluginCustomArgName(i * 8 + 1), tmp_config[i].param1, -65537, 65537); + addFormNumericBox(F("Period"), getPluginCustomArgName(i * 8 + 2), tmp_config[i].param2, 30, 3600); + addUnit('s'); + addFormNumericBox(F("On-Time"), getPluginCustomArgName(i * 8 + 3), tmp_config[i].param3, 0, 3600); + addUnit('s'); + addFormNumericBox(F("Rise-Time"), getPluginCustomArgName(i * 8 + 4), tmp_config[i].param4, 0, 3600); + addUnit('s'); + addFormNumericBox(F("Fall-Time"), getPluginCustomArgName(i * 8 + 5), tmp_config[i].param5, 0, 3600); + addUnit('s'); + break; + case P187_OUTPUT_RANDOM: + addFormNumericBox(F("Max-Level"), getPluginCustomArgName(i * 8 + 0), tmp_config[i].param0, -65537, 65537); + addFormNumericBox(F("Min-Level"), getPluginCustomArgName(i * 8 + 1), tmp_config[i].param1, -65537, 65537); + break; + default: + break; + } + + if (i < (valueCount - 1)) { + addFormSeparator(2); + } + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P187_config_struct tmp_config[VARS_PER_TASK]; + + const int valueCount = getValueCountFromSensorType(static_cast(P187_OUTPUT_TYPE)); + memset(&(tmp_config), 0, sizeof(tmp_config)); + + for (int i = 0; i < valueCount; i++) + { + tmp_config[i].param0 = getFormItemInt(getPluginCustomArgName(i * 8 + 0)); + tmp_config[i].param1 = getFormItemInt(getPluginCustomArgName(i * 8 + 1)); + + switch (P187_OUTPUT_OPTIONx_CONFIG(i)) + { + case P187_OUTPUT_SINUS: + tmp_config[i].param2 = getFormItemInt(getPluginCustomArgName(i * 8 + 2)); + tmp_config[i].param3 = getFormItemInt(getPluginCustomArgName(i * 8 + 3)); + break; + case P187_OUTPUT_TRAPEZ: + tmp_config[i].param2 = getFormItemInt(getPluginCustomArgName(i * 8 + 2)); + tmp_config[i].param3 = getFormItemInt(getPluginCustomArgName(i * 8 + 3)); + tmp_config[i].param4 = getFormItemInt(getPluginCustomArgName(i * 8 + 4)); + tmp_config[i].param5 = getFormItemInt(getPluginCustomArgName(i * 8 + 5)); + + if (tmp_config[i].param3 >= tmp_config[i].param2) + { + tmp_config[i].param3 = tmp_config[i].param2; + tmp_config[i].param4 = 0; + tmp_config[i].param5 = 0; + } + else + { + if ((tmp_config[i].param3 + tmp_config[i].param4 > tmp_config[i].param2) || + (tmp_config[i].param3 + tmp_config[i].param4 + tmp_config[i].param5 > tmp_config[i].param2)) + { + tmp_config[i].param4 = (tmp_config[i].param2 - tmp_config[i].param3) * tmp_config[i].param4 / + (tmp_config[i].param4 + tmp_config[i].param5); + tmp_config[i].param5 = tmp_config[i].param2 - tmp_config[i].param3 - tmp_config[i].param4; + } + } + break; + case P187_OUTPUT_RANDOM: + break; + default: + P187_OUTPUT_OPTIONx_CONFIG(i) = P187_OUTPUT_SINUS; + tmp_config[i].param0 = 100; + tmp_config[i].param2 = 60; + addLog(LOG_LEVEL_ERROR, F("P187: Output type unknown -> reset to default Sinus output")); + break; + } + + } + success = SaveCustomTaskSettings(event->TaskIndex, (uint8_t *)&(tmp_config), sizeof(tmp_config), 0); // save configuration to flash + break; + } + + case PLUGIN_READ: + { + P187_data_struct *P187_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (P187_data != nullptr) && P187_data->plugin_read(event); + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P187_data_struct()); + P187_data_struct *P187_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P187_data) && P187_data->init(event); + break; + } + + } // switch + + return success; +} // function + +#endif // USES_P187 diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 0ddd3935bd..00175ece41 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1888,6 +1888,9 @@ To create/register a plugin, you have to : #if !defined(USES_P178) && defined(ESP32) #define USES_P178 // Extra IO - LU9685 Servo controller #endif + #if !defined(USES_P187) && defined(ESP32) + #define USES_P187 // Generic - Dummy Data Injector + #endif #endif // ifdef PLUGIN_SET_COLLECTION_H // Collection of all energy related plugins. @@ -3007,6 +3010,9 @@ To create/register a plugin, you have to : #ifndef USES_P180 #define USES_P180 // Generic - I2C Generic #endif + #ifndef USES_P187 + #define USES_P187 // Generic - Dummy Data Injector + #endif // Controllers #ifndef USES_C015 diff --git a/src/src/PluginStructs/P187_data_struct.cpp b/src/src/PluginStructs/P187_data_struct.cpp new file mode 100644 index 0000000000..1260fb6897 --- /dev/null +++ b/src/src/PluginStructs/P187_data_struct.cpp @@ -0,0 +1,99 @@ +#include "../PluginStructs/P187_data_struct.h" + +#ifdef USES_P187 + +# include "../Helpers/CRC_functions.h" + +bool P187_data_struct::init(struct EventStruct *event) { + + if (event != nullptr) { + unsigned long now = millis(); // use same point in time for all outputs + LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&(P187_param), sizeof(P187_param)); // load configuration from flash + + for (int i = 0; i < VARS_PER_TASK; i++) { + periodStart[i] = now; // initialize period start time + } + initialized = true; + } + + return isInitialized(); +} + +# define PARAMms2(x) (P187_param[x].param2 * 1000) +# define PARAMms3(x) (P187_param[x].param3 * 1000) +# define PARAMms4(x) (P187_param[x].param4 * 1000) +# define PARAMms5(x) (P187_param[x].param5 * 1000) + +bool P187_data_struct::plugin_read(struct EventStruct *event) { + bool success = false; + + if (isInitialized() && (event != nullptr)) { + unsigned long now = millis(); // use same point in time for all outputs + + const int valueCount = getValueCountFromSensorType(static_cast(P187_OUTPUT_TYPE)); + + for (int i = 0; i < valueCount; i++) + { + int t_ms; // time within period in ms + + if (now < periodStart[i]) { // check for overflow (happens each ~49 days) + t_ms = (now + (ULONG_MAX - periodStart[i])); + } + else { + t_ms = now - periodStart[i]; + } + + if (t_ms > PARAMms2(i)) // period wrap around + { + periodStart[i] = periodStart[i] + PARAMms2(i); + t_ms -= PARAMms2(i); + } + + switch (P187_OUTPUT_OPTIONx_CONFIG(i)) + { + case P187_OUTPUT_SINUS: + UserVar.setFloat(event->TaskIndex, i, + P187_param[i].param1 + + ((float)P187_param[i].param0 * + sinf((2 * PI * (float)t_ms / (float)(PARAMms2(i))) + ((float)P187_param[i].param3 * PI / 180.0f)))); + break; + + case P187_OUTPUT_TRAPEZ: + + if (t_ms < PARAMms4(i)) // rising edge + { + UserVar.setFloat(event->TaskIndex, i, + P187_param[i].param1 + + ((float)(P187_param[i].param0 - P187_param[i].param1) * (float)t_ms / (float)PARAMms4(i))); + } + else if (t_ms < (PARAMms4(i) + PARAMms3(i))) // on level + { + UserVar.setFloat(event->TaskIndex, i, P187_param[i].param0); + } + else if (t_ms < (PARAMms4(i) + PARAMms3(i) + PARAMms5(i))) // falling edge + { + UserVar.setFloat(event->TaskIndex, i, + P187_param[i].param1 + + ((P187_param[i].param0 - P187_param[i].param1) * + (1.0f - (((float)(t_ms - PARAMms4(i) - PARAMms3(i)) / (float)PARAMms5(i)))))); + } + else // off level + { + UserVar.setFloat(event->TaskIndex, + i, + P187_param[i].param1); + } + break; + case P187_OUTPUT_RANDOM: + UserVar.setFloat(event->TaskIndex, i, + P187_param[i].param1 + (random(0, 10000) / 10000.0f) * (P187_param[i].param0 - P187_param[i].param1)); + break; + } + } + success = true; + } + + return success; +} + +#endif // ifdef USES_P187 diff --git a/src/src/PluginStructs/P187_data_struct.h b/src/src/PluginStructs/P187_data_struct.h new file mode 100644 index 0000000000..ea0de9fbe1 --- /dev/null +++ b/src/src/PluginStructs/P187_data_struct.h @@ -0,0 +1,62 @@ +#ifndef PLUGINSTRUCTS_P187_DATA_STRUCT_H +#define PLUGINSTRUCTS_P187_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P187 + +// storage for type of output to generate, e.g. sinus or trapezoid +# define P187_OUTPUT_OPTION_CONFIG_POS 0 +# define P187_OUTPUT_OPTION0_CONFIG PCONFIG(P187_OUTPUT_OPTION_CONFIG_POS + 0) +# define P187_OUTPUT_OPTION1_CONFIG PCONFIG(P187_OUTPUT_OPTION_CONFIG_POS + 1) +# define P187_OUTPUT_OPTION2_CONFIG PCONFIG(P187_OUTPUT_OPTION_CONFIG_POS + 2) +# define P187_OUTPUT_OPTION3_CONFIG PCONFIG(P187_OUTPUT_OPTION_CONFIG_POS + 3) +# define P187_OUTPUT_OPTIONx_CONFIG(x) PCONFIG(P187_OUTPUT_OPTION_CONFIG_POS + x) + +// storage for the type of output to generate, e.g. SENSOR_TYPE_SINGLE, ... +# define P187_OUTPUT_TYPE_INDEX 4 +# define P187_OUTPUT_TYPE PCONFIG(P187_OUTPUT_TYPE_INDEX) +struct P187_config_struct { + int param0; // SINUS_AMPLITUDE, TRAPEZ_ON_LEVEL, RANDOM_MAN + int param1; // SINUS_OFFSET, TRAPEZ_OFF_LEVEL, RANDOM_MIX + int param2; // SINUS_PERIOD, TRAPEZ_PERIOD + int param3; // SINUS_PHASE, TRAPEZ_ON_TIME + int param4; // TRAPEZ_RISE_TIME + int param5; // TRAPEZ_FALL_TIME + +}; + +enum P187_output_options { + // do not modify order of these, as they are used in the code to determine which output is selected + P187_OUTPUT_SINUS = 0, + P187_OUTPUT_TRAPEZ, + P187_OUTPUT_RANDOM, + + // keep as last: + P187_NR_OUTPUT_OPTIONS + +}; + +struct P187_data_struct : public PluginTaskData_base { + P187_data_struct() = default; + + virtual ~P187_data_struct() = default; + + // volatile storage for working copy of output configuration values, e.g. amplitude, offset, period, phase, ... + P187_config_struct P187_param[VARS_PER_TASK]; // parameters for VARS_PER_TASK outputs + + bool init(struct EventStruct *event); + bool plugin_read(struct EventStruct *event); + + bool isInitialized() const { + return initialized; + } + +private: + + bool initialized = false; + unsigned long periodStart[VARS_PER_TASK]; + +}; + +#endif // ifdef USES_P187 +#endif // ifndef PLUGINSTRUCTS_P187_DATA_STRUCT_H