diff --git a/docs/source/Plugin/P092.rst b/docs/source/Plugin/P092.rst
new file mode 100644
index 000000000..1b87fd993
--- /dev/null
+++ b/docs/source/Plugin/P092.rst
@@ -0,0 +1,36 @@
+.. include:: ../Plugin/_plugin_substitutions_p09x.repl
+.. _P092_page:
+
+|P092_typename|
+==================================================
+
+|P092_shortinfo|
+
+Plugin details
+--------------
+
+Type: |P092_type|
+
+Name: |P092_name|
+
+Status: |P092_status|
+
+GitHub: |P092_github|_
+
+Maintainer: |P092_maintainer|
+
+Used libraries: |P092_usedlibraries|
+
+Supported hardware
+------------------
+
+|P092_usedby|
+
+Change log
+----------
+
+.. versionadded:: 1.0
+ ...
+
+ |added|
+ Initial release version.
diff --git a/docs/source/Plugin/P092_12V_Wiring.PNG b/docs/source/Plugin/P092_12V_Wiring.PNG
new file mode 100644
index 000000000..35a2a8434
Binary files /dev/null and b/docs/source/Plugin/P092_12V_Wiring.PNG differ
diff --git a/docs/source/Plugin/P092_24V_Wiring.PNG b/docs/source/Plugin/P092_24V_Wiring.PNG
new file mode 100644
index 000000000..360ed4e4a
Binary files /dev/null and b/docs/source/Plugin/P092_24V_Wiring.PNG differ
diff --git a/docs/source/Plugin/P092_DL_bus_databyte.png b/docs/source/Plugin/P092_DL_bus_databyte.png
new file mode 100644
index 000000000..0a456c607
Binary files /dev/null and b/docs/source/Plugin/P092_DL_bus_databyte.png differ
diff --git a/docs/source/Plugin/P092_DL_bus_dataframe.png b/docs/source/Plugin/P092_DL_bus_dataframe.png
new file mode 100644
index 000000000..03bdf8c1e
Binary files /dev/null and b/docs/source/Plugin/P092_DL_bus_dataframe.png differ
diff --git a/docs/source/Plugin/P092_DL_bus_dataframelog.png b/docs/source/Plugin/P092_DL_bus_dataframelog.png
new file mode 100644
index 000000000..64a7e3217
Binary files /dev/null and b/docs/source/Plugin/P092_DL_bus_dataframelog.png differ
diff --git a/docs/source/Plugin/P092_DL_bus_displayclock.png b/docs/source/Plugin/P092_DL_bus_displayclock.png
new file mode 100644
index 000000000..1bf4d2133
Binary files /dev/null and b/docs/source/Plugin/P092_DL_bus_displayclock.png differ
diff --git a/docs/source/Plugin/P092_DL_bus_outputwiring.png b/docs/source/Plugin/P092_DL_bus_outputwiring.png
new file mode 100644
index 000000000..e6f6d594b
Binary files /dev/null and b/docs/source/Plugin/P092_DL_bus_outputwiring.png differ
diff --git a/docs/source/Plugin/P092_DLbus.rst b/docs/source/Plugin/P092_DLbus.rst
new file mode 100644
index 000000000..232419bd1
--- /dev/null
+++ b/docs/source/Plugin/P092_DLbus.rst
@@ -0,0 +1,50 @@
+.. include:: ../Plugin/_plugin_substitutions_p09x.repl
+.. _P092_DLbus_page:
+
+DL bus
+======
+
+|P092_typename|
+|P092_status|
+
+Introduction
+------------
+
+The DL-Bus is used in control units e.g. sold by `Technische Alternative `_.
+The DL-Bus serves as a bus line for various external sensors and modules.
+The DL-Bus is a bidirectional data line and only compatible with products of Technische Alternative.
+
+The data transmission looks as follows:
+ * In an infinite loop, a logging data frame is created by the control one after the others on the data line.
+
+Up to 4 sensor measured value queries (master/slave) can be made between the individual logging data frames on the DL bus.
+ * So that the beginning of a data frame can be detected, a SYNC of 16 high bits is sent before the first data byte.
+ * The data transmission is carried out as Manchester code (EXOR linked) with a display clock of 50 or 488Hz (depending on control type).
+
+This is necessary to ensure the supply voltage of the logger and DL sensors from the data signal.
+If the receiver is synchronized to the display clock, the correct bit value appears always during the second half period of the data bit (inverted in the first half period).
+
+Wiring
+------
+
+The DL-Bus consists of 2 wires: DL and GND (sensor ground).
+The power supply for the DL-Bus sensors is supplied via the DL-Bus itself.
+Some DL-Bus devices can/must be powered via a 12V source, e.g. that of the CAN bus (this is explicitly noted in the operating manual of that sensor).
+The cable routing can be star-shaped or serial (from one device to the next).
+Any cable with a cross-section of 0.75 mm2 up to a maximum length of 30 m can be used as a data cable.
+Over 30 m, the use of shielded cables is recommended, which increases the permissible length of the cable to 100 m.
+
+Protocol
+--------
+
+Transmission of a data byte
+.. image:: P092_DL_bus_databyte.png
+
+Transmission of a data frame
+.. image:: P092_DL_bus_dataframe.png
+
+Data frame on the DL bus
+.. image:: P092_DL_bus_dataframelog.png
+
+Display clock
+.. image:: P092_DL_bus_displayclock.png
diff --git a/docs/source/Plugin/P092_ESR21.PNG b/docs/source/Plugin/P092_ESR21.PNG
new file mode 100644
index 000000000..0da32e442
Binary files /dev/null and b/docs/source/Plugin/P092_ESR21.PNG differ
diff --git a/docs/source/Plugin/P092_ESR21.rst b/docs/source/Plugin/P092_ESR21.rst
new file mode 100644
index 000000000..ba2af9a8c
--- /dev/null
+++ b/docs/source/Plugin/P092_ESR21.rst
@@ -0,0 +1,76 @@
+.. include:: ../Plugin/_plugin_substitutions_p09x.repl
+.. _P092_ESR21_page:
+
+ESR21
+=====
+
+|P092_typename|
+|P092_status|
+
+Introduction
+------------
+
+The ESR21 unit is a simple solar control.
+The ESR21 unit is a differential control that can be used in many ways
+
+The DL bus runs at 12 volts.
+The data frame consist of 30 bytes (excluding sync and CRC).
+The data are transmitted @ 488Hz, therefore the transmission of one data set takes about 0.67 seconds.
+.. image:: P092_ESR21.png
+
+Wiring
+------
+
+.. image:: P092_12V_Wiring.png
+
+Protocol
+--------
+
+.. image:: P092_ESR21_Protocol.png
+
+Setup
+-----
+
+.. note:: A new device task must be created for each value to be read from the DL bus!
+
+.. image:: P092_Setup.png
+
+Task settings
+~~~~~~~~~~~~~
+
+* **Device**: Heating - DL-Bus (Technische Alternative)
+* **Name**: Name of the task (example name **ValueN**, where N is a number).
+* **Enable**: Should the task be enabled or not
+
+Sensor
+^^^^^^
+
+* **1st GPIO**: DL bus input e.g. **GPIO 14 (D5)**
+* **DL-Bus Type**: **ESR21**
+
+.. note:: The settings for **1st GPIO** and **DL-Bus Type** must be the same for all used DL bus tasks!
+
+Inputs
+^^^^^^
+
+.. note:: The description of the **Frame data** and the related **Frame bytes** can be found in the section **Protocol**!
+
+.. csv-table::
+ :header: "Frame data", "Frame bytes", "Value", "Index", "Decimals"
+ :widths: 16, 8, 8, 5, 5
+
+ "Sensor1 ... Sensor3", "3 ... 8", "Sensor", "1 - 3", "1"
+ "Ext1 ... Ext6", "9 ... 20", "Ext. Sensor", "1 - 6", "1"
+ "Output byte", "21", "Digital Output", "", "0"
+ "Speed step", "22", "Speed step", "", "0"
+ "Analog output", "23", "Analog output", "", "1"
+ "Current power", "25,26", "Heat power [kW]", "", "1"
+ "Heat quantity", "27 ... 30", "Heat meter [MWh]", "", "4"
+
+Data acquisition
+^^^^^^^^^^^^^^^^
+
+* **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:: Intervall not less than **10 seconds**! The reading of the DL bus happens twice per intervall.
diff --git a/docs/source/Plugin/P092_ESR21_Protocol.PNG b/docs/source/Plugin/P092_ESR21_Protocol.PNG
new file mode 100644
index 000000000..8ec2c9055
Binary files /dev/null and b/docs/source/Plugin/P092_ESR21_Protocol.PNG differ
diff --git a/docs/source/Plugin/P092_Setup.png b/docs/source/Plugin/P092_Setup.png
new file mode 100644
index 000000000..a543b6335
Binary files /dev/null and b/docs/source/Plugin/P092_Setup.png differ
diff --git a/docs/source/Plugin/P092_UVR1611.png b/docs/source/Plugin/P092_UVR1611.png
new file mode 100644
index 000000000..d9cdabfa0
Binary files /dev/null and b/docs/source/Plugin/P092_UVR1611.png differ
diff --git a/docs/source/Plugin/P092_UVR1611.rst b/docs/source/Plugin/P092_UVR1611.rst
new file mode 100644
index 000000000..efd456823
--- /dev/null
+++ b/docs/source/Plugin/P092_UVR1611.rst
@@ -0,0 +1,80 @@
+.. include:: ../Plugin/_plugin_substitutions_p09x.repl
+.. _P092_UVR1611_page:
+
+UVR1611
+=======
+
+|P092_typename|
+|P092_status|
+
+Introduction
+------------
+
+The UVR1611 unit is a freely programmable universal control system.
+The UVR1611 universal controller can be freely programmed using function modules to match any system configuration for heating room management.
+
+The DL bus runs at 12 volts.
+The data frame consist of 63 bytes (excluding sync and CRC).
+The data are transmitted @ 488Hz, therefore the transmission of one data set takes about 1.4 seconds.
+.. image:: P092_UVR1611.png
+
+Wiring
+------
+
+.. image:: P092_12V_Wiring.png
+
+Protocol
+--------
+
+.. image:: P092_UVR1611_Protocol.png
+
+Setup
+-----
+
+.. note:: A new device task must be created for each value to be read from the DL bus!
+
+.. image:: P092_Setup.png
+
+Task settings
+~~~~~~~~~~~~~
+
+* **Device**: Heating - DL-Bus (Technische Alternative)
+* **Name**: Name of the task (example name **ValueN**, where N is a number).
+* **Enable**: Should the task be enabled or not
+
+Sensor
+^^^^^^
+
+* **1st GPIO**: DL bus input e.g. **GPIO 14 (D5)**
+* **DL-Bus Type**: **UVR1611**
+
+.. note:: The settings for **1st GPIO** and **DL-Bus Type** must be the same for all used DL bus tasks!
+
+Inputs
+^^^^^^
+
+.. note:: The description of the **Frame data** and the related **Frame bytes** can be found in the section **Protocol**!
+
+.. csv-table::
+ :header: "Frame data", "Frame bytes", "Value", "Index", "Decimals"
+ :widths: 16, 8, 8, 5, 5
+
+ "Sensor1 ... Sensor16", "9 ... 40", "Sensor", "1 - 16", "1"
+ "Output A1 ... A7", "41", "Digital Output", "1 - 7", "0"
+ "Output A9 ... A13", "42", "Digital Output", "9 - 13", "0"
+ "Speed step A1", "43", "Speed step", "1", "0"
+ "Speed step A2", "44", "Speed step", "2", "0"
+ "Speed step A6", "45", "Speed step", "3", "0"
+ "Speed step A7", "46", "Speed step", "4", "0"
+ "Current power 1", "48 ... 51", "Heat power [kW]", "1", "2"
+ "Current power 2", "56 ... 59", "Heat power [kW]", "2", "2"
+ "Heat quantity 1", "52 ... 55", "Heat meter [MWh]", "1", "4"
+ "Heat quantity 2", "60 ... 64", "Heat meter [MWh]", "2", "4"
+
+Data acquisition
+^^^^^^^^^^^^^^^^
+
+* **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:: Intervall not less than **10 seconds**! The reading of the DL bus happens twice per intervall.
diff --git a/docs/source/Plugin/P092_UVR1611_Protocol.png b/docs/source/Plugin/P092_UVR1611_Protocol.png
new file mode 100644
index 000000000..498b08584
Binary files /dev/null and b/docs/source/Plugin/P092_UVR1611_Protocol.png differ
diff --git a/docs/source/Plugin/P092_UVR31.PNG b/docs/source/Plugin/P092_UVR31.PNG
new file mode 100644
index 000000000..9365ba1a1
Binary files /dev/null and b/docs/source/Plugin/P092_UVR31.PNG differ
diff --git a/docs/source/Plugin/P092_UVR31.rst b/docs/source/Plugin/P092_UVR31.rst
new file mode 100644
index 000000000..b7748a8f7
--- /dev/null
+++ b/docs/source/Plugin/P092_UVR31.rst
@@ -0,0 +1,71 @@
+.. include:: ../Plugin/_plugin_substitutions_p09x.repl
+.. _P092_UVR31_page:
+
+UVR31
+=====
+
+|P092_typename|
+|P092_status|
+
+Introduction
+------------
+
+The UVR31 unit is a single-circuit universal control system.
+The UVR31 unit is designed for simple solar installations and heating systems (storage charging, domestic hot water preparation)
+
+The DL bus runs at 24 volts.
+The data frame consist of 8 bytes (excluding sync).
+The data are transmitted @ 50Hz, therefore the transmission of one data set takes about 1.92 seconds.
+.. image:: P092_UVR31.png
+
+Wiring
+------
+
+.. image:: P092_24V_Wiring.png
+
+Protocol
+--------
+
+.. image:: P092_UVR31_Protocol.png
+
+Setup
+-----
+
+.. note:: A new device task must be created for each value to be read from the DL bus!
+
+.. image:: P092_Setup.png
+
+Task settings
+~~~~~~~~~~~~~
+
+* **Device**: Heating - DL-Bus (Technische Alternative)
+* **Name**: Name of the task (example name **ValueN**, where N is a number).
+* **Enable**: Should the task be enabled or not
+
+Sensor
+^^^^^^
+
+* **1st GPIO**: DL bus input e.g. **GPIO 14 (D5)**
+* **DL-Bus Type**: **UVR31**
+
+.. note:: The settings for **1st GPIO** and **DL-Bus Type** must be the same for all used DL bus tasks!
+
+Inputs
+^^^^^^
+
+.. note:: The description of the **Frame data** and the related **Frame bytes** can be found in the section **Protocol**!
+
+.. csv-table::
+ :header: "Frame data", "Frame bytes", "Value", "Index", "Decimals"
+ :widths: 16, 8, 8, 5, 5
+
+ "Temp1 ... Temp3", "2 ... 7", "Sensor", "1 - 3", "1"
+ "Output byte", "8", "Digital Output", "", "0"
+
+Data acquisition
+^^^^^^^^^^^^^^^^
+
+* **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:: Intervall not less than **20 seconds**! The reading of the DL bus happens twice per intervall.
diff --git a/docs/source/Plugin/P092_UVR31_Protocol.PNG b/docs/source/Plugin/P092_UVR31_Protocol.PNG
new file mode 100644
index 000000000..85d6eab05
Binary files /dev/null and b/docs/source/Plugin/P092_UVR31_Protocol.PNG differ
diff --git a/docs/source/Plugin/P092_UVR61-3.png b/docs/source/Plugin/P092_UVR61-3.png
new file mode 100644
index 000000000..830c22b39
Binary files /dev/null and b/docs/source/Plugin/P092_UVR61-3.png differ
diff --git a/docs/source/Plugin/P092_UVR61-3.rst b/docs/source/Plugin/P092_UVR61-3.rst
new file mode 100644
index 000000000..1995671ac
--- /dev/null
+++ b/docs/source/Plugin/P092_UVR61-3.rst
@@ -0,0 +1,76 @@
+.. include:: ../Plugin/_plugin_substitutions_p09x.repl
+.. _P092_UVR61-3_page:
+
+UVR61-3
+=======
+
+|P092_typename|
+|P092_status|
+
+Introduction
+------------
+
+The UVR61-3 unit is a three-circuit universal control system.
+The UVR61-3 unit is designed for an energy-saving and cost-effective drying of cellars and other parts of buildings by means of fan control.
+The UVR61-3 three-circuit controllers has various thermostat, differential temperature and speed control functions for use in solar and heating systems, too.
+
+The DL bus runs at 12 volts.
+The data frame consist of 34 bytes (excluding sync and CRC).
+The data are transmitted @ 488Hz, therefore the transmission of one data set takes about 0.8 seconds.
+.. image:: P092_UVR61-3.png
+
+Wiring
+------
+
+.. image:: P092_12V_Wiring.png
+
+Protocol
+--------
+
+.. image:: P092_UVR61-3_Protocol.png
+
+Setup
+-----
+
+.. note:: A new device task must be created for each value to be read from the DL bus!
+
+.. image:: P092_Setup.png
+
+Task settings
+~~~~~~~~~~~~~
+
+* **Device**: Heating - DL-Bus (Technische Alternative)
+* **Name**: Name of the task (example name **ValueN**, where N is a number).
+* **Enable**: Should the task be enabled or not
+
+Sensor
+^^^^^^
+
+* **1st GPIO**: DL bus input e.g. **GPIO 14 (D5)**
+* **DL-Bus Type**: **UVR61-3 v8.2**
+
+.. note:: The settings for **1st GPIO** and **DL-Bus Type** must be the same for all used DL bus tasks!
+
+Inputs
+^^^^^^
+
+.. note:: The description of the **Frame data** and the related **Frame bytes** can be found in the section **Protocol**!
+
+.. csv-table::
+ :header: "Frame data", "Frame bytes", "Value", "Index", "Decimals"
+ :widths: 16, 8, 8, 5, 5
+
+ "Sensor1 ... Sensor6", "9 ... 20", "Sensor", "1 - 6", "1"
+ "Output A1 ... A3", "21", "Digital Output", "1 - 3", "0"
+ "Speed step", "22", "Speed step", "", "0"
+ "Analog output", "23", "Analog output", "", "1"
+ "Current power", "27,28", "Heat power [kW]", "", "1"
+ "Heat quantity", "29 ... 34", "Heat meter [MWh]", "", "4"
+
+Data acquisition
+^^^^^^^^^^^^^^^^
+
+* **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:: Intervall not less than **10 seconds**! The reading of the DL bus happens twice per intervall.
diff --git a/docs/source/Plugin/P092_UVR61-3_Protocol.png b/docs/source/Plugin/P092_UVR61-3_Protocol.png
new file mode 100644
index 000000000..359648f8d
Binary files /dev/null and b/docs/source/Plugin/P092_UVR61-3_Protocol.png differ
diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst
index d86d6af7e..212b7694e 100644
--- a/docs/source/Plugin/_Plugin.rst
+++ b/docs/source/Plugin/_Plugin.rst
@@ -114,6 +114,7 @@ There's three different released versions of ESP Easy:
":ref:`P088_page`","|P088_status|","P088"
":ref:`P089_page`","|P089_status|","P089"
":ref:`P090_page`","|P090_status|","P090"
+ ":ref:`P092_page`","|P092_status|","P092"
Internal GPIO handling
diff --git a/docs/source/Plugin/_plugin_substitutions_p09x.repl b/docs/source/Plugin/_plugin_substitutions_p09x.repl
index f9d9fba8c..cb74d283e 100644
--- a/docs/source/Plugin/_plugin_substitutions_p09x.repl
+++ b/docs/source/Plugin/_plugin_substitutions_p09x.repl
@@ -8,4 +8,15 @@
.. |P090_shortinfo| replace:: `.`
.. |P090_maintainer| replace:: `TD-er`
.. |P090_compileinfo| replace:: `.`
-.. |P090_usedlibraries| replace:: `.`
\ No newline at end of file
+.. |P090_usedlibraries| replace:: `.`
+.. |P092_name| replace:: :cyan:`DL-Bus (Technische Alternative)`
+.. |P092_type| replace:: :cyan:`Heating`
+.. |P092_typename| replace:: :cyan:`Heating - DL-Bus (Technische Alternative)`
+.. |P092_status| replace:: :yellow:`TESTING`
+.. |P092_github| replace:: P092_DLbus.ino
+.. _P092_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P092_DLbus.ino
+.. |P092_usedby| replace:: `.`
+.. |P092_shortinfo| replace:: `.`
+.. |P092_maintainer| replace:: `.`
+.. |P092_compileinfo| replace:: `.`
+.. |P092_usedlibraries| replace:: `.`
diff --git a/lib/DLBus/DLBus.cpp b/lib/DLBus/DLBus.cpp
new file mode 100644
index 000000000..e041af332
--- /dev/null
+++ b/lib/DLBus/DLBus.cpp
@@ -0,0 +1,372 @@
+#include "DLBus.h"
+//
+// DLBus reads and decodes the DL-Bus.
+// The DL-Bus is used in heating control units e.g. sold by Technische Alternative (www.ta.co.at).
+// Author uwekaditz
+
+//#define DLbus_DEBUG
+
+// Flags for pulse width (bit 0 is the content!)
+#define DLbus_FlagSingleWidth 0x02
+#define DLbus_FlagDoubleWidth 0x04
+#define DLbus_FlagShorterThanSingleWidth 0x10
+#define DLbus_FlagBetweenDoubleSingleWidth 0x20
+#define DLbus_FlagLongerThanDoubleWidth 0x40
+#define DLbus_FlagLongerThanTwiceDoubleWidth 0x80
+#define DLbus_FlagsWrongTiming (DLbus_FlagLongerThanTwiceDoubleWidth | DLbus_FlagLongerThanDoubleWidth | DLbus_FlagBetweenDoubleSingleWidth | DLbus_FlagShorterThanSingleWidth)
+
+extern long usecPassedSince(unsigned long timestamp) ICACHE_RAM_ATTR;
+extern void addToLog(byte loglevel, const String& string);
+
+//#define LOG_LEVEL_ERROR 1
+#define LOG_LEVEL_INFO 2
+#ifndef F
+// Create a no-op F() macro so the code base still compiles outside of the
+// Arduino framework. Thus we can safely use the Arduino 'F()' macro through-out
+// the code base. That macro stores constants in Flash (PROGMEM) memory.
+// See: https://github.com/crankyoldgit/IRremoteESP8266/issues/667
+ #define F(x) x
+#endif // F
+
+// Helper for ISR call
+DLBus* DLBus::__instance = nullptr;
+volatile static uint8_t* ISR_PtrChangeBitStream = nullptr; // pointer to received bit change stream
+
+DLBus::DLBus()
+{
+ if (__instance == nullptr)
+ {
+ __instance = this;
+ ISR_PtrChangeBitStream = DLbus_ChangeBitStream;
+ addToLog(LOG_LEVEL_INFO, F("Class DLBus created"));
+ }
+}
+
+DLBus::~DLBus()
+{
+ if (__instance == this)
+ {
+ __instance = nullptr;
+ ISR_PtrChangeBitStream = nullptr;
+ addToLog(LOG_LEVEL_INFO, F("Class DLBus destroyed"));
+ }
+}
+
+void DLBus::AddToInfoLog(const String& string)
+{
+ if ((IsLogLevelInfo) && (LogLevelInfo != 0xff))
+ addToLog(LogLevelInfo, string);
+}
+
+void DLBus::AddToErrorLog(const String& string)
+{
+ if (LogLevelError != 0xff)
+ addToLog(LogLevelError, string);
+}
+
+void DLBus::attachDLBusInterrupt(void)
+{
+ ISR_Receiving = false;
+ attachInterrupt(digitalPinToInterrupt(ISR_DLB_Pin), ISR, CHANGE);
+}
+
+void DLBus::StartReceiving(void)
+{
+ noInterrupts (); // make sure we don't get interrupted before we are ready
+ ISR_PulseCount = 0;
+ ISR_Receiving = (ISR_PtrChangeBitStream != nullptr);
+ ISR_AllBitsReceived = false;
+ interrupts (); // interrupts allowed now, next instruction WILL be executed
+}
+
+void ICACHE_RAM_ATTR DLBus::ISR(void)
+{
+ if (__instance)
+ {
+ __instance->ISR_PinChanged();
+ }
+}
+
+void ICACHE_RAM_ATTR DLBus::ISR_PinChanged(void)
+{
+ long TimeDiff = usecPassedSince(ISR_TimeLastBitChange); // time difference to previous pulse in µs
+ ISR_TimeLastBitChange = micros(); // save last pin change time
+ if (ISR_Receiving) {
+ uint8_t val = digitalRead(ISR_DLB_Pin); // read state
+ // check pulse width
+ if (TimeDiff >= 2*ISR_MinDoublePulseWidth) val |= DLbus_FlagLongerThanTwiceDoubleWidth; // longer then 2x double pulse width
+ else if (TimeDiff > ISR_MaxDoublePulseWidth) val |= DLbus_FlagLongerThanDoubleWidth; // longer then double pulse width
+ else if (TimeDiff >= ISR_MinDoublePulseWidth) val |= DLbus_FlagDoubleWidth; // double pulse width
+ else if (TimeDiff > ISR_MaxPulseWidth) val |= DLbus_FlagBetweenDoubleSingleWidth; // between double and single pulse width
+ else if (TimeDiff < ISR_MinPulseWidth) val |= DLbus_FlagShorterThanSingleWidth; // shorter then single pulse width
+ else val |= DLbus_FlagSingleWidth; // single pulse width
+ if (ISR_PulseCount < 2) {
+ // check if sync is received
+ if (val & DLbus_FlagLongerThanTwiceDoubleWidth) {
+ // sync received
+ *ISR_PtrChangeBitStream = !(val & 0x01);
+ *(ISR_PtrChangeBitStream+1) = val;
+ ISR_PulseCount = 2;
+ }
+ else
+ ISR_PulseCount = 1; // flag that interrupt is receiving
+ }
+ else {
+ *(ISR_PtrChangeBitStream+ISR_PulseCount) = val; // set bit
+ ISR_PulseCount++;
+ ISR_Receiving = (ISR_PulseCount < ISR_PulseNumber); // stop P092_receiving when data frame is complete
+ ISR_AllBitsReceived = !ISR_Receiving;
+ }
+ }
+}
+
+boolean DLBus::CheckTimings(void) {
+ uint8_t rawval, val;
+ uint8_t WrongTimeCnt = 0;
+ int i;
+#ifdef DLbus_DEBUG
+ uint16_t WrongTimingArray[5][6];
+#endif // DLbus_DEBUG
+
+// AddToInfoLog(F("Receive stopped."));
+
+ ISR_PulseCount = 0;
+ for (i = 0; i <= ISR_PulseNumber; i++) {
+ // store DLbus_ChangeBitStream into ByteStream
+ rawval = *(ISR_PtrChangeBitStream+i);
+ if (rawval & DLbus_FlagsWrongTiming) {
+ // wrong DLbus_time_diff
+ if (ISR_PulseCount > 0) {
+#ifdef DLbus_DEBUG
+ WrongTimingArray[WrongTimeCnt][0] = i;
+ WrongTimingArray[WrongTimeCnt][1] = ISR_PulseCount;
+ WrongTimingArray[WrongTimeCnt][2] = BitNumber;
+ WrongTimingArray[WrongTimeCnt][3] = rawval;
+#endif // DLbus_DEBUG
+ if ((rawval == DLbus_FlagLongerThanTwiceDoubleWidth) && (*(ISR_PtrChangeBitStream+i-1) == (DLbus_FlagDoubleWidth | 0x01))) {
+ // Add two additional short pulses (low and high), previous bit is High and contains DLbus_FlagDoubleWidth
+ ProcessBit(0);
+ ProcessBit(1);
+#ifdef DLbus_DEBUG
+ WrongTimingArray[WrongTimeCnt][4] = DLbus_FlagSingleWidth;
+ WrongTimingArray[WrongTimeCnt][5] = DLbus_FlagSingleWidth+1;
+#endif // DLbus_DEBUG
+ }
+#ifdef DLbus_DEBUG
+ else {
+ WrongTimingArray[WrongTimeCnt][4] = 0xff;
+ WrongTimingArray[WrongTimeCnt][5] = 0xff;
+ }
+#endif // DLbus_DEBUG
+ WrongTimeCnt++;
+ if (WrongTimeCnt >=5)
+ return false;
+ }
+ }
+ else {
+ val = rawval & 0x01;
+ if ((rawval & DLbus_FlagDoubleWidth) == DLbus_FlagDoubleWidth) {
+ // double pulse width
+ ProcessBit(!val);
+ ProcessBit(val);
+ }
+ else {
+ // single pulse width
+ ProcessBit(val);
+ }
+ }
+ }
+// AddToInfoLog(F("DLbus_ChangeBitStream copied."));
+
+#ifdef DLbus_DEBUG
+ if (WrongTimeCnt > 0) {
+ if (IsLogLevelInfo) {
+ String log = F("Wrong Timings: ");
+ AddToInfoLog(log);
+ for (i = 0; i < WrongTimeCnt; i++) {
+ log = i + 1;
+ log += F(": PulseCount:");
+ log += WrongTimingArray[i][1];
+ log += F(": BitCount:");
+ log += WrongTimingArray[i][2];
+ log += F(" Value:0x");
+ log += String(WrongTimingArray[i][3], HEX);
+ log += F(" ValueBefore:0x");
+ log += String(*(ISR_PtrChangeBitStream+WrongTimingArray[i][0] - 1), HEX);
+ log += F(" ValueAfter:0x");
+ log += String(*(ISR_PtrChangeBitStream+WrongTimingArray[i][0] + 1), HEX);
+ if (WrongTimingArray[i][4]!=0xff) {
+ log += F(" Added:0x");
+ log += String(WrongTimingArray[i][4], HEX);
+ }
+ if (WrongTimingArray[i][5]!=0xff) {
+ log += F(" Added:0x");
+ log += String(WrongTimingArray[i][5], HEX);
+ }
+ AddToInfoLog(log);
+ }
+ }
+ }
+#endif // DLbus_DEBUG
+ return true;
+}
+
+void DLBus::ProcessBit(uint8_t b) {
+ // ignore first pulse
+ ISR_PulseCount++;
+ if (ISR_PulseCount % 2)
+ return;
+ BitNumber = (ISR_PulseCount / 2);
+ if (b)
+ ByteStream[BitNumber / 8] |= (1 << (BitNumber % 8)); // set bit
+ else
+ ByteStream[BitNumber / 8] &= ~(1 << (BitNumber % 8)); // clear bit
+}
+
+boolean DLBus::Processing(void) {
+boolean inverted = false;
+int16_t StartBit; // first bit of data frame (-1 not recognized)
+String log;
+ AddToInfoLog(F("Processing..."));
+ StartBit = Analyze(); // find the data frame's beginning
+ // inverted signal?
+ while (StartBit == -1) {
+ if (inverted) {
+ AddToErrorLog(F("Error: Already inverted!"));
+ return false;
+ }
+ Invert(); // invert again
+ inverted = true;
+ StartBit = Analyze();
+ if (StartBit == -1) {
+ AddToErrorLog(F("Error: No data frame available!"));
+ return false;
+ }
+ uint16_t RequiredBitStreamLength = (ISR_PulseNumber - DLBus_ReserveBytes)/DLBus_BitChangeFactor;
+ if ((BitNumber-StartBit) < RequiredBitStreamLength) {
+ // no complete data frame available (difference between start_bit and received bits is < RequiredBitStreamLength)
+ AddToErrorLog(F("Start bit too close to end of stream!"));
+ if (IsLogLevelInfo) {
+ log = F("# Required bits: ");
+ log += RequiredBitStreamLength;
+ log += F(" StartBit: ");
+ log += StartBit;
+ log += F(" / EndBit: ");
+ log += BitNumber;
+ AddToInfoLog(log);
+ }
+ return false;
+ }
+ }
+
+ if (IsLogLevelInfo) {
+ log = F("StartBit: ");
+ log += StartBit;
+ log += F(" / EndBit: ");
+ log += BitNumber;
+ AddToInfoLog(log);
+ }
+ Trim(StartBit); // remove start and stop bits
+
+ if (CheckDevice()) // check connected device
+ return true;
+ else {
+ AddToErrorLog(F("Error: Device not found!"));
+ return false;
+ }
+}
+
+int DLBus::Analyze(void) {
+ uint8_t sync=0;
+ // find SYNC (16 * sequential 1)
+ for (int i = 0; i < BitNumber; i++) {
+ if (ReadBit(i))
+ sync++;
+ else
+ sync = 0;
+ if (sync == DLBus_SyncBits) {
+ // finde erste 0 // find first 0
+ while (ReadBit(i) == 1)
+ i++;
+ return i; // beginning of data frame
+ }
+ }
+ // no data frame available. check signal?
+ return -1;
+}
+
+void DLBus::Invert(void) {
+ AddToInfoLog(F("Invert bit stream..."));
+ for (int i = 0; i < BitNumber; i++)
+ WriteBit(i, ReadBit(i) ? 0 : 1); // invert every bit
+}
+
+uint8_t DLBus::ReadBit(int pos) {
+ int row = pos / 8; // detect position in bitmap
+ int col = pos % 8;
+ return (((ByteStream[row]) >> (col)) & 0x01); // return bit
+}
+
+void DLBus::WriteBit(int pos, uint8_t set) {
+ int row = pos / 8; // detect position in bitmap
+ int col = pos % 8;
+ if (set)
+ ByteStream[row] |= 1 << col; // set bit
+ else
+ ByteStream[row] &= ~(1 << col); // clear bit
+}
+
+void DLBus::Trim(int start_bit) {
+ for (int i = start_bit, bit = 0; i < BitNumber; i++) {
+ int offset = i - start_bit;
+ // ignore start and stop bits:
+ // start bits: 0 10 20 30, also x % 10 == 0
+ // stop bits: 9 19 29 39, also (x+1) % 10 == 0
+ if (offset % 10 && (offset + 1) % 10) {
+ WriteBit(bit, ReadBit(i));
+ bit++;
+ }
+ }
+}
+
+boolean DLBus::CheckDevice(void) {
+ // Data frame of a device?
+ if (ByteStream[0] == DeviceBytes[0]) {
+ if ((DeviceBytes[1] == 0) || (ByteStream[1] == DeviceBytes[1]))
+ return true;
+ }
+ if (IsLogLevelInfo) {
+ String log = F("# Received DeviceByte(s): 0x");
+ log += String(ByteStream[0], HEX);
+ if (DeviceBytes[1] != 0)
+ log += String(ByteStream[1], HEX);
+ log += F(" Requested: 0x");
+ log += String(DeviceBytes[0], HEX);
+ if (DeviceBytes[1] != 0)
+ log += String(DeviceBytes[1], HEX);
+ AddToInfoLog(log);
+ }
+ return false;
+}
+
+boolean DLBus::CheckCRC(uint8_t IdxCRC) {
+ // CRC check sum
+ if (IdxCRC == 0)
+ return true;
+ AddToInfoLog(F("Check CRC..."));
+ uint16_t dataSum = 0;
+ for (int i = 0; i < IdxCRC; i++)
+ dataSum = dataSum + ByteStream[i];
+ dataSum = dataSum & 0xff;
+ if (dataSum == ByteStream[IdxCRC])
+ return true;
+ AddToErrorLog(F("Check CRC failed!"));
+ if (IsLogLevelInfo) {
+ String log = F("# Calculated CRC: 0x");
+ log += String(dataSum, HEX);
+ log += F(" Received: 0x");
+ log += String(ByteStream[IdxCRC], HEX);
+ AddToInfoLog(log);
+ }
+ return false;
+}
diff --git a/lib/DLBus/DLBus.h b/lib/DLBus/DLBus.h
new file mode 100644
index 000000000..5e8ef99af
--- /dev/null
+++ b/lib/DLBus/DLBus.h
@@ -0,0 +1,65 @@
+#ifndef DLBus_H
+#define DLBus_H
+
+#include
+
+/*********************************************************************************************\
+ DLBus subs to get values from the receiving bitstream
+\*********************************************************************************************/
+// one data frame has data bytes + SYNC, e.g. 64 * (8+1+1) + 16 = 656
+// 656 * 2 = 1312 (twice as much as a data frame is saved
+// so there's one complete data frame
+
+#define DLbus_MaxDataBytes 64
+#define DLbus_AdditionalRecBytes 2
+#define DLbus_StopBits 1
+#define DLbus_StartBits 1
+#define DLBus_SyncBits 16
+#define DLBus_ReserveBytes 20
+#define DLBus_BitChangeFactor 2
+#define DLbus_MaxDataBits (((DLbus_MaxDataBytes + DLbus_AdditionalRecBytes) * (DLbus_StartBits + 8 + DLbus_StopBits) + DLBus_SyncBits) * DLBus_BitChangeFactor) + DLBus_ReserveBytes
+ // MaxDataBits is double of the maximum bit length because each bit change is stored
+ // (((64+2) * (8+1+1) + 16) * 2) + 50 = 1402 bytes
+
+class DLBus
+{
+ public:
+ DLBus(); // constructor of DLBus object
+ ~DLBus(); // destructor of DLBus object
+
+ volatile uint8_t ISR_DLB_Pin = 0xFF;
+ volatile boolean ISR_Receiving = false; // receiving flag
+ volatile boolean ISR_AllBitsReceived = false;
+ volatile uint16_t ISR_PulseCount; // number of received pulses
+ volatile uint16_t ISR_PulseNumber; // max naumber of the received pulses
+ volatile uint16_t ISR_MinPulseWidth, ISR_MaxPulseWidth, ISR_MinDoublePulseWidth, ISR_MaxDoublePulseWidth;
+ // identification bytes for each DL bus device
+ uint8_t DeviceBytes[2];
+ uint8_t ByteStream[DLbus_MaxDataBits / 8 + 1]; // every bit gets sorted into a bitmap
+ boolean IsLogLevelInfo = false;
+ uint8_t LogLevelInfo = 0xff;
+ uint8_t LogLevelError = 0xFF;
+ void attachDLBusInterrupt(void);
+ void StartReceiving(void);
+ boolean CheckTimings(void);
+ boolean Processing(void);
+ boolean CheckCRC(uint8_t IdxCRC);
+ private:
+ volatile uint32_t ISR_TimeLastBitChange = 0; // remember time of last transition
+ uint8_t DLbus_ChangeBitStream[DLbus_MaxDataBits]; // received bit change stream (each bit change is extended to uint8_t, containing the timing flags)
+ uint16_t BitNumber; // bit number of the received DLbus_ChangeBitStream
+ static void ISR(void);
+ void ISR_PinChanged(void);
+ void ProcessBit(uint8_t b);
+ int Analyze(void);
+ void Invert(void);
+ uint8_t ReadBit(int pos);
+ void WriteBit(int pos, uint8_t set);
+ void Trim(int start_bit);
+ boolean CheckDevice(void);
+ static DLBus* __instance;
+ void AddToInfoLog(const String& string);
+ void AddToErrorLog(const String& string);
+};
+
+#endif
diff --git a/lib/DLBus/keywords.txt b/lib/DLBus/keywords.txt
new file mode 100644
index 000000000..d0c8c4bb0
--- /dev/null
+++ b/lib/DLBus/keywords.txt
@@ -0,0 +1,6 @@
+DLBus KEYWORD1
+attachDLBusInterrupt KEYWORD2
+StartReceiving KEYWORD2
+CheckTimings KEYWORD2
+Processing KEYWORD2
+CheckCRC KEYWORD2
diff --git a/src/ESPEasy.ino b/src/ESPEasy.ino
index 27fafd955..e4efc7acd 100644
--- a/src/ESPEasy.ino
+++ b/src/ESPEasy.ino
@@ -81,7 +81,7 @@
// BMP280 I2C Barometric Pressure sensor
// SHT1X temperature/humidity sensors
// Ser2Net server
-
+// DL-Bus (Technische Alternative)
// Define globals before plugin sets to allow a personal override of the selected plugins
#include "ESPEasy-Globals.h"
diff --git a/src/_P092_DLbus.ino b/src/_P092_DLbus.ino
new file mode 100644
index 000000000..71ee90b62
--- /dev/null
+++ b/src/_P092_DLbus.ino
@@ -0,0 +1,947 @@
+#ifdef USES_P092
+
+//#######################################################################################################
+//########################### Plugin 092: DL-bus from Technische Alternative ############################
+//#######################################################################################################
+
+/**************************************************\
+ This plug-in reads and decodes the DL-Bus.
+ The DL-Bus is used in heating control units e.g. sold by Technische Alternative (www.ta.co.at).
+
+ The idea for this plug-in is based on Martin Kropf's project UVR31_RF24 (https://github.com/martinkropf/UVR31_RF24)
+
+ The plug-in is tested and workis fine for the ESR21 device.
+ The plug-in should be also able to decode the information from the
+ UVR31, UVR1611 and UVR 61-3 devices.
+
+ The selected input needs a voltage divider as follows because the DL-Bus runs on 12 volts for
+ following devices: UVR31, UVR42, UVR64, HZR65, EEG30 and TFM66
+ DLbus@12V - 8k6 - input@3.3V - 3k3 - ground
+
+ For following devices just a pull up resistor is needed if the device is used stand alone:
+ UVR1611, UVR61-3 and ESR21
+
+ @uwekaditz 2020-03-01 Memory usage optimized
+ CHG: Moved arrays into the class DLBus
+ CHG: Moved arrays to PLUGIN_092_DEBUG
+
+ @uwekaditz 2019-12-15 Memory usage optimized
+ CHG: Moved the array for the received bit changes to stativ uint_8t, the ISR call uses only a volatile pointer to it
+ CHG: some more defines and name changes for better explanation
+
+ @uwekaditz 2019-12-14 Timing optimized
+ CHG: Removed the while (P092_receiving) loop.
+ CHG: Starting of the receiving and processing of the received bit stream are now done in the PLUGIN_ONCE_A_SECOND call
+ PLUGIN_READ call just uses the already processed data
+
+ @uwekaditz 2019-12-08 Inital commit to mega
+
+\**************************************************/
+
+#include
+
+#define PLUGIN_092
+#define PLUGIN_ID_092 92
+//#define PLUGIN_092_DEBUG // additional debug messages in the log
+#define PLUGIN_NAME_092 "Heating - DL-Bus (Technische Alternative)"
+#define PLUGIN_VALUENAME1_092 "Value"
+
+#define P092_DLbus_OptionCount 8
+#define P092_DLbus_ValueCount 1
+#define P092_DLbus_DeviceCount 5
+
+uint8_t P092_Last_DLB_Pin;
+boolean P092_init = false;
+boolean P092_ReceivedOK = false;
+uint32_t P092_LastReceived = 0;
+struct _P092_DataStruct
+{
+ uint8_t DataBytes;
+ uint8_t DeviceByte0;
+ uint8_t DeviceByte1;
+ uint8_t DeviceBytes;
+ uint8_t DontCareBytes;
+ uint8_t TimeStampBytes;
+ uint8_t MaxSensors;
+ uint8_t MaxExtSensors;
+ uint8_t OutputBytes;
+ uint8_t SpeedBytes;
+ uint8_t AnalogBytes;
+ uint8_t VolumeBytes;
+ uint8_t MaxHeatMeters;
+ uint8_t CurrentHmBytes;
+ uint8_t MWhBytes;
+
+ uint16_t DLbus_MinPulseWidth;
+ uint16_t DLbus_MaxPulseWidth;
+ uint16_t DLbus_MinDoublePulseWidth;
+ uint16_t DLbus_MaxDoublePulseWidth;
+ uint8_t IdxSensor;
+ uint8_t IdxExtSensor;
+ uint8_t IdxOutput;
+ uint8_t IdxDrehzahl;
+ uint8_t IdxAnalog;
+ uint8_t IdxHmRegister;
+ uint8_t IdxVolume;
+ uint8_t IdxHeatMeter1;
+ uint8_t IdxkWh1;
+ uint8_t IdxMWh1;
+ uint8_t IdxHeatMeter2;
+ uint8_t IdxkWh2;
+ uint8_t IdxMWh2;
+ uint8_t IdxHeatMeter3;
+ uint8_t IdxkWh3;
+ uint8_t IdxMWh3;
+ uint8_t IdxCRC;
+} P092_DataSettings;
+
+typedef struct {
+ uint8_t Idx;
+ uint8_t mode;
+ float value;
+} sP092_ReadData;
+
+DLBus *DLbus_Data = nullptr;
+
+// decoding the manchester code
+// pulse width @ 488hz: 1000ms/488 = 2,048ms = 2048µs
+// 2048µs / 2 = 1024µs (2 pulses for one bit)
+// pulse width @ 50hz: 1000ms/50 = 20ms = 20000µs
+// 20000µs / 2 = 10000µs (2 pulses for one bit)
+#define P092_pulse_width_488 1024 // µs
+#define P092_pulse_width_50 10000 // µs
+
+// % tolerance for variances at the pulse width
+#define P092_percentage_variance 10
+// 1001 or 0110 are two sequential pulses without transition
+#define P092_double_pulse_width_488 (P092_pulse_width_488 * 2)
+#define P092_double_pulse_width_50 (P092_pulse_width_50 * 2)
+
+// calculating the tolerance limits for variances
+#define P092_min_width_488 (P092_pulse_width_488 - (P092_pulse_width_488 * P092_percentage_variance / 100))
+#define P092_max_width_488 (P092_pulse_width_488 + (P092_pulse_width_488 * P092_percentage_variance / 100))
+#define P092_double_min_width_488 (P092_double_pulse_width_488 - (P092_pulse_width_488 * P092_percentage_variance / 100))
+#define P092_double_max_width_488 (P092_double_pulse_width_488 + (P092_pulse_width_488 * P092_percentage_variance / 100))
+#define P092_min_width_50 (P092_pulse_width_50 - (P092_pulse_width_50 * P092_percentage_variance / 100))
+#define P092_max_width_50 (P092_pulse_width_50 + (P092_pulse_width_50 * P092_percentage_variance / 100))
+#define P092_double_min_width_50 (P092_double_pulse_width_50 - (P092_pulse_width_50 * P092_percentage_variance / 100))
+#define P092_double_max_width_50 (P092_double_pulse_width_50 + (P092_pulse_width_50 * P092_percentage_variance / 100))
+
+boolean P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData* ReadData);
+
+boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string)
+{
+ boolean success = false;
+ int OptionIdx, CurIdx;
+ sP092_ReadData P092_ReadData;
+
+ uint8_t P092_MaxIdx[P092_DLbus_OptionCount];
+ int P092_ValueType, P092_ValueIdx;
+ int P092_OptionValueDecimals[P092_DLbus_OptionCount] = {
+ // Dezimalstellen der Variablen
+ 0, //F("None")
+ 1, //[0,1°C] F("Sensor")
+ 1, //[0,1°C] F("Ext. sensor")
+ 0, // F("Digital output")
+ 0, // F("Speed step")
+ 1, //[0,1V] F("Analog output")
+ 1, //[0,1kW] F("Heat power (kW)") Attention: UVR1611 in 0,01kW
+ 4 //[0,0001MWh] F("Heat meter (MWh)")
+ };
+
+
+ const String plugin_092_ValStr = F("p092_Value");
+ const String plugin_092_IdxStr = F("p092_Idx");
+
+
+ switch (function)
+ {
+ case PLUGIN_DEVICE_ADD:
+ {
+ Device[++deviceCount].Number = PLUGIN_ID_092;
+ Device[deviceCount].Type = DEVICE_TYPE_SINGLE;
+ Device[deviceCount].VType = SENSOR_TYPE_SINGLE;
+ Device[deviceCount].Ports = 0;
+ Device[deviceCount].PullUpOption = false;
+ Device[deviceCount].InverseLogicOption = false;
+ Device[deviceCount].FormulaOption = false;
+ Device[deviceCount].ValueCount = P092_DLbus_ValueCount;
+ Device[deviceCount].SendDataOption = true;
+ Device[deviceCount].TimerOption = true;
+ Device[deviceCount].GlobalSyncOption = true;
+ Device[deviceCount].DecimalsOnly = true;
+ break;
+ }
+
+ case PLUGIN_GET_DEVICENAME:
+ {
+ string = F(PLUGIN_NAME_092);
+ break;
+ }
+
+ case PLUGIN_GET_DEVICEVALUENAMES:
+ {
+ strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_092));
+ break;
+ }
+
+ case PLUGIN_WEBFORM_LOAD:
+ {
+ P092_Last_DLB_Pin = CONFIG_PIN1;
+
+ const String plugin_092_DefValueName = F(PLUGIN_VALUENAME1_092);
+ const int P092_OptionTypes[P092_DLbus_OptionCount] = {
+ // Index der Variablen
+ 0, //F("None")
+ 1, //F("Sensor")
+ 2, //F("Ext. sensor")
+ 3, //F("Digital output")
+ 4, //F("Speed step")
+ 5, //F("Analog output")
+ 6, //F("Heat power (kW)")
+ 7 //F("Heat meter (MWh)")
+ };
+ const String Options[P092_DLbus_OptionCount] = {
+ F("None"),
+ F("Sensor"),
+ F("Ext. sensor"),
+ F("Digital output"),
+ F("Speed step"),
+ F("Analog output"),
+ F("Heat power (kW)"),
+ F("Heat meter (MWh)")
+ };
+ const String Devices[P092_DLbus_DeviceCount] = { F("ESR21"), F("UVR31"), F("UVR1611"), F("UVR 61-3 (bis V8.2)"), F("UVR 61-3 (ab V8.3)") };
+ const int DevTypes[P092_DLbus_DeviceCount] = { 21, 31, 1611, 6132, 6133 };
+
+ addFormSelector(F("DL-Bus Type"), F("p092_dlbtype"), P092_DLbus_DeviceCount, Devices, DevTypes, NULL, PCONFIG(0), true );
+
+ // Calculation of the max indices for each sensor type
+ // default indizes for UVR31
+ P092_MaxIdx[0] = 0; // None
+ P092_MaxIdx[1] = 3; // Sensor
+ P092_MaxIdx[2] = 0; // Ext. sensor
+ P092_MaxIdx[3] = 1; // Digital output
+ P092_MaxIdx[4] = 0; // Speed step
+ P092_MaxIdx[5] = 0; // Analog output
+ P092_MaxIdx[6] = 0; // Heat power (kW)
+ P092_MaxIdx[7] = 0; // Heat meter (MWh)
+ switch (PCONFIG(0)) {
+ case 21: //ESR21
+ P092_MaxIdx[2] = 6; // Ext. sensor
+ P092_MaxIdx[4] = 1; // Speed step
+ P092_MaxIdx[5] = 1; // Analog output
+ P092_MaxIdx[6] = 1; // Heat power (kW)
+ P092_MaxIdx[7] = 1; // Heat meter (MWh)
+ break;
+ case 1611: //UVR1611
+ P092_MaxIdx[1] = 16; // Sensor
+ P092_MaxIdx[3] = 13; // Digital output
+ P092_MaxIdx[4] = 4; // Speed step
+ P092_MaxIdx[6] = 2; // Heat power (kW)
+ P092_MaxIdx[7] = 2; // Heat meter (MWh)
+ break;
+ case 6132: //UVR 61-3 (bis V8.2)
+ P092_MaxIdx[1] = 6; // Sensor
+ P092_MaxIdx[3] = 8; // Digital output
+ P092_MaxIdx[4] = 1; // Speed step
+ P092_MaxIdx[5] = 1; // Analog output
+ P092_MaxIdx[6] = 1; // Heat power (kW)
+ P092_MaxIdx[7] = 1; // Heat meter (MWh)
+ break;
+ case 6133: //UVR 61-3 (ab V8.3)
+ P092_MaxIdx[1] = 6; // Sensor
+ P092_MaxIdx[2] = 9; // Ext. sensor
+ P092_MaxIdx[3] = 3; // Digital output
+ P092_MaxIdx[4] = 1; // Speed step
+ P092_MaxIdx[5] = 2; // Analog output
+ P092_MaxIdx[6] = 3; // Heat power (kW)
+ P092_MaxIdx[7] = 3; // Heat meter (MWh)
+ break;
+ }
+
+ addFormSubHeader(F("Inputs"));
+
+ for (int i = 0; i < P092_DLbus_ValueCount; i++) {
+ P092_ValueType = PCONFIG(i + 1) >> 8;
+ P092_ValueIdx = PCONFIG(i + 1) & 0x00FF;
+
+ addFormSelector(plugin_092_DefValueName, plugin_092_ValStr, P092_DLbus_OptionCount, Options, P092_OptionTypes, NULL, P092_ValueType, true);
+ if (P092_MaxIdx[P092_ValueType] > 1) {
+ CurIdx = P092_ValueIdx;
+ if (CurIdx < 1) {
+ CurIdx = 1;
+ }
+ if (CurIdx > P092_MaxIdx[P092_ValueType]) {
+ CurIdx = P092_MaxIdx[P092_ValueType];
+ }
+ addHtml(F(" Index: "));
+ addNumericBox(plugin_092_IdxStr, CurIdx, 1, P092_MaxIdx[P092_ValueType]);
+ }
+ }
+
+ UserVar[event->BaseVarIndex] = NAN;
+
+ success = true;
+ break;
+ }
+
+ case PLUGIN_WEBFORM_SAVE:
+ {
+ PCONFIG(0) = getFormItemInt(F("p092_dlbtype"));
+ if (PCONFIG(0) == 1611) // only UVR1611
+ P092_OptionValueDecimals[6] = 2;
+
+ for (int i = 0; i < P092_DLbus_ValueCount; i++) {
+ OptionIdx = getFormItemInt(plugin_092_ValStr);
+ CurIdx = getFormItemInt(plugin_092_IdxStr);
+ if (CurIdx < 1) {
+ CurIdx = 1;
+ }
+ PCONFIG(i + 1) = (OptionIdx << 8) + CurIdx;
+ ExtraTaskSettings.TaskDeviceValueDecimals[event->BaseVarIndex + i] = P092_OptionValueDecimals[OptionIdx];
+ }
+
+ if (DLbus_Data == nullptr) {
+ addLog(LOG_LEVEL_ERROR, F("## P092_save: Error DL-Bus: Class not initialized!"));
+ return false;
+ }
+
+ if (P092_Last_DLB_Pin != CONFIG_PIN1) {
+ // pin number is changed -> run a new init
+ P092_init = false;
+ if (DLbus_Data->ISR_DLB_Pin != 0xFF) {
+ // interrupt was already attached to P092_DLB_Pin
+ detachInterrupt(digitalPinToInterrupt(DLbus_Data->ISR_DLB_Pin));
+ addLog(LOG_LEVEL_INFO, F("P092_save: detachInterrupt"));
+ }
+ }
+
+#ifdef PLUGIN_092_DEBUG
+ if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+
+ String log = F("PLUGIN_WEBFORM_SAVE :");
+ log += F(" DLB_Pin:");
+ log += CONFIG_PIN1;
+ log += F(" DLbus_MinPulseWidth:");
+ log += P092_DataSettings.DLbus_MinPulseWidth;
+ log += F(" DLbus_MaxPulseWidth:");
+ log += P092_DataSettings.DLbus_MaxPulseWidth;
+ log += F(" DLbus_MinDoublePulseWidth:");
+ log += P092_DataSettings.DLbus_MinDoublePulseWidth;
+ log += F(" DLbus_MaxDoublePulseWidth:");
+ log += P092_DataSettings.DLbus_MaxDoublePulseWidth;
+ log += F(" IdxSensor:");
+ log += P092_DataSettings.IdxSensor;
+ log += F(" IdxExtSensor:");
+ log += P092_DataSettings.IdxExtSensor;
+ log += F(" IdxOutput:");
+ log += P092_DataSettings.IdxOutput;
+ if (P092_DataSettings.SpeedBytes > 0) {
+ log += F(" IdxDrehzahl:");
+ log += P092_DataSettings.IdxDrehzahl;
+ }
+ if (P092_DataSettings.AnalogBytes > 0) {
+ log += F(" IdxAnalog:");
+ log += P092_DataSettings.IdxAnalog;
+ }
+ if (P092_DataSettings.MaxHeatMeters > 0) {
+ log += F(" IdxHmRegister:");
+ log += P092_DataSettings.IdxHmRegister;
+ }
+ if (P092_DataSettings.VolumeBytes > 0) {
+ log += F(" IdxVolume:");
+ log += P092_DataSettings.IdxVolume;
+ }
+ if (P092_DataSettings.MaxHeatMeters > 0) {
+ log += F(" IdxHM1:");
+ log += P092_DataSettings.IdxHeatMeter1;
+ log += F(" IdxkWh1:");
+ log += P092_DataSettings.IdxkWh1;
+ log += F(" IdxMWh1:");
+ log += P092_DataSettings.IdxMWh1;
+ }
+ if (P092_DataSettings.MaxHeatMeters > 1) {
+ log += F(" IdxHM2:");
+ log += P092_DataSettings.IdxHeatMeter2;
+ log += F(" IdxkWh2:");
+ log += P092_DataSettings.IdxkWh2;
+ log += F(" IdxMWh2:");
+ log += P092_DataSettings.IdxMWh2;
+ }
+ if (P092_DataSettings.MaxHeatMeters > 2) {
+ log += F(" IdxHM3:");
+ log += P092_DataSettings.IdxHeatMeter3;
+ log += F(" IdxkWh3:");
+ log += P092_DataSettings.IdxkWh3;
+ log += F(" IdxMWh3:");
+ log += P092_DataSettings.IdxMWh3;
+ }
+ log += F(" IdxCRC:");
+ log += P092_DataSettings.IdxCRC;
+ addLog(LOG_LEVEL_INFO, log);
+ }
+#endif // PLUGIN_092_DEBUG
+ UserVar[event->BaseVarIndex] = NAN;
+ success = true;
+ break;
+ }
+
+ case PLUGIN_INIT:
+ {
+ if (P092_init) {
+ addLog(LOG_LEVEL_ERROR, F("P092_init -> Already done!"));
+ }
+ else {
+ addLog(LOG_LEVEL_INFO, F("P092_init ..."));
+ if (DLbus_Data == nullptr) {
+ DLbus_Data = new DLBus;
+ if (DLbus_Data == nullptr) {
+ addLog(LOG_LEVEL_ERROR, F("## P092_init: Error DL-Bus: Class not initialized!"));
+ return false;
+ }
+ DLbus_Data->LogLevelInfo = LOG_LEVEL_INFO;
+ DLbus_Data->LogLevelError = LOG_LEVEL_ERROR;
+ DLbus_Data->IsLogLevelInfo = loglevelActiveFor(LOG_LEVEL_INFO);
+ }
+ P092_init = true;
+ P092_ReceivedOK = false;
+ addLog(LOG_LEVEL_INFO, F("P092_init: attachInterrupt"));
+ DLbus_Data->ISR_DLB_Pin = CONFIG_PIN1;
+ pinMode(CONFIG_PIN1, INPUT_PULLUP);
+ // on a CHANGE on the data pin P092_Pin_changed is called
+ DLbus_Data->attachDLBusInterrupt();
+ UserVar[event->BaseVarIndex] = NAN;
+ }
+ success = true;
+ break;
+ }
+
+ case PLUGIN_ONCE_A_SECOND:
+ {
+ if (WiFi.status() != WL_CONNECTED) {
+ return false;
+ }
+ if (P092_init == false) {
+ return false;
+ }
+ if (DLbus_Data == nullptr)
+ return false;
+ if (DLbus_Data->ISR_Receiving) {
+ return false;
+ }
+ Plugin_092_SetIndices(PCONFIG(0));
+ if (DLbus_Data->ISR_AllBitsReceived) {
+ DLbus_Data->ISR_AllBitsReceived = false;
+ success = DLbus_Data->CheckTimings();
+ if (success)
+ success = DLbus_Data->Processing();
+ if (success)
+ success = DLbus_Data->CheckCRC(P092_DataSettings.IdxCRC);
+ if (success) {
+ addLog(LOG_LEVEL_INFO, F("Received data OK"));
+ P092_LastReceived = millis();
+ }
+ P092_ReceivedOK = success;
+ }
+ else
+ success = P092_ReceivedOK;
+
+ if ((P092_ReceivedOK == false) || (timePassedSince(P092_LastReceived)>(static_cast(Settings.TaskDeviceTimer[event->TaskIndex] * 1000/2)))) {
+ Plugin_092_StartReceiving();
+ success = true;
+ }
+ break;
+ }
+
+ case PLUGIN_READ:
+ {
+ addLog(LOG_LEVEL_ERROR, F("PLUGIN_092_READ"));
+ if (WiFi.status() != WL_CONNECTED) {
+ // too busy for DLbus while wifi connect is running
+ addLog(LOG_LEVEL_ERROR, F("## P092_read: Error DL-Bus: WiFi not connected!"));
+ return false;
+ }
+ if (P092_init == false) {
+ addLog(LOG_LEVEL_ERROR, F("## P092_read: Error DL-Bus: Not initialized!"));
+ return false;
+ }
+ if (DLbus_Data == nullptr) {
+ addLog(LOG_LEVEL_ERROR, F("## P092_read: Error DL-Bus: Class not initialized!"));
+ return false;
+ }
+ if (DLbus_Data->ISR_DLB_Pin != CONFIG_PIN1) {
+ String log = F("## P092_read: Error DL-Bus: Device Pin setting not correct!");
+ log += F(" DLB_Pin:");
+ log += DLbus_Data->ISR_DLB_Pin;
+ log += F(" Setting:");
+ log += CONFIG_PIN1;
+ addLog(LOG_LEVEL_ERROR, log);
+ return false;
+ }
+ success = P092_ReceivedOK;
+ if (P092_ReceivedOK == false) {
+ addLog(LOG_LEVEL_INFO, F("P092_read: Still receiving DL-Bus bits!"));
+ }
+ else {
+ for (int i = 0; i < P092_DLbus_ValueCount; i++) {
+ OptionIdx = PCONFIG(i + 1) >> 8;
+ CurIdx = PCONFIG(i + 1) & 0x00FF;
+ if (P092_GetData(OptionIdx, CurIdx, &P092_ReadData)) {
+ UserVar[event->BaseVarIndex + i] = P092_ReadData.value;
+ }
+ else {
+ addLog(LOG_LEVEL_ERROR, F("## P092_read: Error: No readings!"));
+ }
+ }
+ }
+ break;
+ }
+ }
+ return success;
+}
+
+void Plugin_092_SetIndices(int DeviceIndex) {
+ // Set the indices for the DL bus packet
+int iDeviceBytes, iDontCareBytes, iTimeStampBytes;
+
+ //default settings for ESR21
+ P092_DataSettings.DataBytes = 31;
+ P092_DataSettings.DLbus_MinPulseWidth = P092_min_width_488;
+ P092_DataSettings.DLbus_MaxPulseWidth = P092_max_width_488;
+ P092_DataSettings.DLbus_MinDoublePulseWidth = P092_double_min_width_488;
+ P092_DataSettings.DLbus_MaxDoublePulseWidth = P092_double_max_width_488;
+
+ P092_DataSettings.DeviceByte0 = 0x70;
+ P092_DataSettings.DeviceByte1 = 0x8F;
+ iDeviceBytes = 2;
+ iDontCareBytes = 0;
+ iTimeStampBytes = 0;
+ P092_DataSettings.MaxSensors = 3;
+ P092_DataSettings.MaxExtSensors = 6;
+ P092_DataSettings.OutputBytes = 1;
+ P092_DataSettings.SpeedBytes = 1;
+ P092_DataSettings.AnalogBytes = 1;
+ P092_DataSettings.VolumeBytes = 0;
+ P092_DataSettings.MaxHeatMeters = 1;
+ P092_DataSettings.CurrentHmBytes = 2;
+ P092_DataSettings.MWhBytes = 2;
+ P092_DataSettings.IdxCRC = 30;
+
+ switch (DeviceIndex) {
+ case 31: //UVR31
+ P092_DataSettings.DataBytes = 8;
+ P092_DataSettings.DLbus_MinPulseWidth = P092_min_width_50;
+ P092_DataSettings.DLbus_MaxPulseWidth = P092_max_width_50;
+ P092_DataSettings.DLbus_MinDoublePulseWidth = P092_double_min_width_50;
+ P092_DataSettings.DLbus_MaxDoublePulseWidth = P092_double_max_width_50;
+
+ P092_DataSettings.DeviceByte0 = 0x30;
+ P092_DataSettings.DeviceByte1 = 0;
+ iDeviceBytes = 1;
+ P092_DataSettings.MaxExtSensors = 0;
+ P092_DataSettings.SpeedBytes = 0;
+ P092_DataSettings.AnalogBytes = 0;
+ P092_DataSettings.MaxHeatMeters = 0;
+ P092_DataSettings.CurrentHmBytes = 0;
+ P092_DataSettings.MWhBytes = 0;
+ P092_DataSettings.IdxCRC = 0;
+ break;
+ case 1611: //UVR1611
+ P092_DataSettings.DataBytes = 64;
+
+ P092_DataSettings.DeviceByte0 = 0x80;
+ P092_DataSettings.DeviceByte1 = 0x7F;
+ iDontCareBytes = 1;
+ iTimeStampBytes = 5;
+ P092_DataSettings.MaxSensors = 16;
+ P092_DataSettings.MaxExtSensors = 0;
+ P092_DataSettings.OutputBytes = 2;
+ P092_DataSettings.SpeedBytes = 4;
+ P092_DataSettings.AnalogBytes = 0;
+ P092_DataSettings.MaxHeatMeters = 2;
+ P092_DataSettings.CurrentHmBytes = 4;
+ P092_DataSettings.IdxCRC = P092_DataSettings.DataBytes-1;
+
+ break;
+ case 6132: //UVR 61-3 (up to V8.2)
+ P092_DataSettings.DataBytes = 35;
+
+ P092_DataSettings.DeviceByte0 = 0x90;
+ P092_DataSettings.DeviceByte1 = 0x6F;
+ iDontCareBytes = 1;
+ iTimeStampBytes = 5;
+ P092_DataSettings.MaxSensors = 6;
+ P092_DataSettings.MaxExtSensors = 0;
+ P092_DataSettings.VolumeBytes = 2;
+ P092_DataSettings.MWhBytes = 4;
+ P092_DataSettings.IdxCRC = P092_DataSettings.DataBytes-1;
+
+ break;
+ case 6133: //UVR 61-3 (from V8.3)
+ P092_DataSettings.DataBytes = 62;
+
+ P092_DataSettings.DeviceByte0 = 0x90;
+ P092_DataSettings.DeviceByte1 = 0x9F;
+ iDontCareBytes = 1;
+ iTimeStampBytes = 5;
+ P092_DataSettings.MaxSensors = 6;
+ P092_DataSettings.MaxExtSensors = 9;
+ P092_DataSettings.MaxHeatMeters = 3;
+ P092_DataSettings.IdxCRC = P092_DataSettings.DataBytes-1;
+
+ break;
+ }
+ P092_DataSettings.IdxSensor = iDeviceBytes + iDontCareBytes + iTimeStampBytes;
+ P092_DataSettings.IdxExtSensor = P092_DataSettings.IdxSensor + 2 * P092_DataSettings.MaxSensors;
+ P092_DataSettings.IdxOutput = P092_DataSettings.IdxExtSensor + 2 * P092_DataSettings.MaxExtSensors;
+ P092_DataSettings.IdxDrehzahl = P092_DataSettings.IdxOutput + P092_DataSettings.OutputBytes;
+ P092_DataSettings.IdxAnalog = P092_DataSettings.IdxDrehzahl + P092_DataSettings.SpeedBytes;
+ P092_DataSettings.IdxHmRegister = P092_DataSettings.IdxAnalog + P092_DataSettings.AnalogBytes;
+ P092_DataSettings.IdxVolume = P092_DataSettings.IdxHmRegister + 1;
+ P092_DataSettings.IdxHeatMeter1 = P092_DataSettings.IdxVolume + P092_DataSettings.VolumeBytes;
+ P092_DataSettings.IdxkWh1 = P092_DataSettings.IdxHeatMeter1 + P092_DataSettings.CurrentHmBytes;
+ P092_DataSettings.IdxMWh1 = P092_DataSettings.IdxkWh1 + 2;
+ P092_DataSettings.IdxHeatMeter2 = P092_DataSettings.IdxMWh1 + P092_DataSettings.MWhBytes;
+ P092_DataSettings.IdxkWh2 = P092_DataSettings.IdxHeatMeter2 + P092_DataSettings.CurrentHmBytes;
+ P092_DataSettings.IdxMWh2 = P092_DataSettings.IdxkWh2 + 2;
+ P092_DataSettings.IdxHeatMeter3 = P092_DataSettings.IdxMWh2 + P092_DataSettings.MWhBytes;
+ P092_DataSettings.IdxkWh3 = P092_DataSettings.IdxHeatMeter3 + P092_DataSettings.CurrentHmBytes;
+ P092_DataSettings.IdxMWh3 = P092_DataSettings.IdxkWh3 + 2;
+
+ return;
+}
+/*********************************************************************************************\
+ DLBus subs to get values from the P092_receiving bitstream
+\*********************************************************************************************/
+
+// sensor types
+#define DLbus_UNUSED 0b000
+#define DLbus_Sensor_DIGITAL 0b001
+#define DLbus_Sensor_TEMP 0b010
+#define DLbus_Sensor_VOLUME_FLOW 0b011
+#define DLbus_Sensor_RAYS 0b110
+#define DLbus_Sensor_ROOM 0b111
+
+// room sensor modes
+#define DLbus_RSM_AUTO 0b00
+#define DLbus_RSM_NORMAL 0b01
+#define DLbus_RSM_LOWER 0b10
+#define DLbus_RSM_STANDBY 0b11
+
+boolean P092_fetch_sensor(int number, sP092_ReadData* ReadData);
+boolean P092_fetch_output(int number, sP092_ReadData* ReadData); // digital output byte(s)
+boolean P092_fetch_speed(int number, sP092_ReadData* ReadData); // speed byte(s)
+boolean P092_fetch_analog(int number, sP092_ReadData* ReadData); // analog byte(s)
+boolean P092_fetch_heatpower(int number, sP092_ReadData* ReadData); // heat power(s)
+boolean P092_fetch_heatmeter(int number, sP092_ReadData* ReadData); // heat meters(s)
+
+// heat meter
+typedef struct {
+ uint8_t IndexIsValid;
+ int32_t power_index;
+ int32_t kwh_index;
+ int32_t mwh_index;
+} sDLbus_HMindex;
+sDLbus_HMindex P092_CheckHmRegister(int number);
+
+/****************\
+ DLBus P092_receiving
+\****************/
+
+void Plugin_092_StartReceiving(void) {
+ DLbus_Data->ISR_Receiving = false;
+ DLbus_Data->DeviceBytes[0] = P092_DataSettings.DeviceByte0;
+ DLbus_Data->DeviceBytes[1] = P092_DataSettings.DeviceByte1;
+ DLbus_Data->ISR_PulseNumber = (((P092_DataSettings.DataBytes + DLbus_AdditionalRecBytes) * (DLbus_StartBits + 8 + DLbus_StopBits) + DLBus_SyncBits) * DLBus_BitChangeFactor) + DLBus_ReserveBytes;
+ DLbus_Data->ISR_MinPulseWidth = P092_DataSettings.DLbus_MinPulseWidth;
+ DLbus_Data->ISR_MaxPulseWidth = P092_DataSettings.DLbus_MaxPulseWidth;
+ DLbus_Data->ISR_MinDoublePulseWidth = P092_DataSettings.DLbus_MinDoublePulseWidth;
+ DLbus_Data->ISR_MaxDoublePulseWidth = P092_DataSettings.DLbus_MaxDoublePulseWidth;
+ DLbus_Data->StartReceiving();
+ uint32_t start=millis();
+ addLog(LOG_LEVEL_INFO, F("P092_receiving ..."));
+ while ((timePassedSince(start)<100) && (DLbus_Data->ISR_PulseCount == 0)) {
+ // wait for first pulse received (timeout 100ms)
+ yield();
+ }
+ if (DLbus_Data->ISR_PulseCount == 0) {
+ // nothing received
+ DLbus_Data->ISR_Receiving = false;
+ addLog(LOG_LEVEL_ERROR, F("## StartReceiving: Error: Nothing received! No DL bus connected!"));
+ }
+}
+
+/****************\
+ DLBus get data
+\****************/
+
+boolean P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData* ReadData) {
+ String log;
+ boolean result = false;
+ switch (OptionIdx) {
+ case 1: //F("Sensor")
+ log = F("Get Sensor");
+ log += CurIdx;
+ if (CurIdx > P092_DataSettings.MaxSensors) {
+ result=false;
+ break;
+ }
+ ReadData->Idx = P092_DataSettings.IdxSensor;
+ result = P092_fetch_sensor(CurIdx, ReadData);
+ break;
+ case 2: //F("Sensor")
+ log = F("Get ExtSensor");
+ log += CurIdx;
+ if (CurIdx > P092_DataSettings.MaxExtSensors) {
+ result=false;
+ break;
+ }
+ ReadData->Idx = P092_DataSettings.IdxExtSensor;
+ result = P092_fetch_sensor(CurIdx, ReadData);
+ break;
+ case 3: //F("Digital output")
+ log = F("Get DigitalOutput");
+ log += CurIdx;
+ if (CurIdx > (8 * P092_DataSettings.OutputBytes)) {
+ result=false;
+ break;
+ }
+ result = P092_fetch_output(CurIdx, ReadData);
+ break;
+ case 4: //F("Speed step")
+ log = F("Get SpeedStep");
+ log += CurIdx;
+ if (CurIdx > P092_DataSettings.SpeedBytes) {
+ result=false;
+ break;
+ }
+ result = P092_fetch_speed(CurIdx, ReadData);
+ break;
+ case 5: //F("Analog output")
+ log = F("Get AnalogOutput");
+ log += CurIdx;
+ if (CurIdx > P092_DataSettings.AnalogBytes) {
+ result=false;
+ break;
+ }
+ result = P092_fetch_analog(CurIdx, ReadData);
+ break;
+ case 6: //F("Heat power (kW)")
+ log = F("Get HeatPower");
+ log += CurIdx;
+ if (CurIdx > P092_DataSettings.MaxHeatMeters) {
+ result=false;
+ break;
+ }
+ result = P092_fetch_heatpower(CurIdx, ReadData);
+ break;
+ case 7: //F("Heat meter (MWh)"
+ log = F("Get HeatMeter");
+ log += CurIdx;
+ if (CurIdx > P092_DataSettings.MaxHeatMeters) {
+ result=false;
+ break;
+ }
+ result = P092_fetch_heatmeter(CurIdx, ReadData);
+ break;
+ }
+ if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+ log += F(": ");
+ if (result) {
+ log += String(ReadData->value, 1);
+ }
+ else {
+ log += F("nan");
+ }
+ addLog(LOG_LEVEL_INFO, log);
+ }
+ return result;
+}
+
+boolean P092_fetch_sensor(int number, sP092_ReadData* ReadData) {
+ float value;
+ ReadData->mode = -1;
+ number = ReadData->Idx + (number - 1) * 2;
+ int32_t sensorvalue = (DLbus_Data->ByteStream[number + 1] << 8) | DLbus_Data->ByteStream[number];
+ if (sensorvalue == 0) {
+ return false;
+ }
+ uint8_t sensortype = (sensorvalue & 0x7000) >> 12;
+ if (!(sensorvalue & 0x8000)) { // sign positive
+ sensorvalue &= 0xfff;
+ // calculations for different sensor types
+ switch (sensortype) {
+ case DLbus_Sensor_DIGITAL:
+ value = false;
+ break;
+ case DLbus_Sensor_TEMP:
+ value = sensorvalue * 0.1;
+ break;
+ case DLbus_Sensor_RAYS:
+ value = sensorvalue;
+ break;
+ case DLbus_Sensor_VOLUME_FLOW:
+ value = sensorvalue * 4;
+ break;
+ case DLbus_Sensor_ROOM:
+ ReadData->mode = (sensorvalue & 0x600) >> 9;
+ value = (sensorvalue & 0x1ff) * 0.1;
+ break;
+ default:
+ return false;
+ }
+ }
+ else { // sign negative
+ sensorvalue |= 0xf000;
+ // calculations for different sensor types
+ switch (sensortype) {
+ case DLbus_Sensor_DIGITAL:
+ value = true;
+ break;
+ case DLbus_Sensor_TEMP:
+ value = (sensorvalue - 0x10000) * 0.1;
+ break;
+ case DLbus_Sensor_RAYS:
+ value = sensorvalue - 0x10000;
+ break;
+ case DLbus_Sensor_VOLUME_FLOW:
+ value = (sensorvalue - 0x10000) * 4;
+ break;
+ case DLbus_Sensor_ROOM:
+ ReadData->mode = (sensorvalue & 0x600) >> 9;
+ value = ((sensorvalue & 0x1ff) - 0x10000) * 0.1;
+ break;
+ default:
+ return false;
+ }
+ }
+ ReadData->value = value;
+ return true;
+}
+
+boolean P092_fetch_output(int number, sP092_ReadData* ReadData) {
+ int32_t outputs;
+
+ if (P092_DataSettings.OutputBytes > 1)
+ outputs = (DLbus_Data->ByteStream[P092_DataSettings.IdxOutput + 1] << 8) | DLbus_Data->ByteStream[P092_DataSettings.IdxOutput];
+ else
+ outputs = DLbus_Data->ByteStream[P092_DataSettings.IdxOutput];
+ if (outputs & (1 << (number - 1)))
+ ReadData->value = 1;
+ else
+ ReadData->value = 0;
+ return true;
+}
+
+boolean P092_fetch_speed(int number, sP092_ReadData* ReadData) {
+ uint8_t speedbyte;
+
+ if ((P092_DataSettings.IdxDrehzahl + (number - 1)) >= P092_DataSettings.IdxAnalog) {
+ // wrong index for speed, overlapping next index (IdxAnalog)
+ return false;
+ }
+ speedbyte = DLbus_Data->ByteStream[P092_DataSettings.IdxDrehzahl + (number - 1)];
+ if (speedbyte & 0x80)
+ return false;
+ ReadData->value = (speedbyte & 0x1f);
+ return true;
+}
+
+boolean P092_fetch_analog(int number, sP092_ReadData* ReadData) {
+ uint8_t analogbyte;
+
+ if ((P092_DataSettings.IdxAnalog + (number - 1)) >= P092_DataSettings.IdxHmRegister) {
+ // wrong index for analog, overlapping next index (IdxHmRegister)
+ return false;
+ }
+ analogbyte = DLbus_Data->ByteStream[P092_DataSettings.IdxAnalog + (number - 1)];
+ if (analogbyte & 0x80)
+ return false;
+ ReadData->value = (analogbyte * 0.1);
+ return true;
+}
+
+sDLbus_HMindex P092_CheckHmRegister(int number) {
+sDLbus_HMindex result;
+
+ result.IndexIsValid = 0;
+ switch (number) {
+ case 1:
+ if ((DLbus_Data->ByteStream[P092_DataSettings.IdxHmRegister] & 0x1) == 0)
+ return result;
+ result.power_index = P092_DataSettings.IdxHeatMeter1;
+ result.kwh_index = P092_DataSettings.IdxkWh1;
+ result.mwh_index = P092_DataSettings.IdxMWh1;
+ break;
+ case 2:
+ if ((DLbus_Data->ByteStream[P092_DataSettings.IdxHmRegister] & 0x2) == 0)
+ return result;
+ result.power_index = P092_DataSettings.IdxHeatMeter2;
+ result.kwh_index = P092_DataSettings.IdxkWh2;
+ result.mwh_index = P092_DataSettings.IdxMWh2;
+ break;
+ case 3:
+ if ((DLbus_Data->ByteStream[P092_DataSettings.IdxHmRegister] & 0x4) == 0)
+ return result;
+ result.power_index = P092_DataSettings.IdxHeatMeter3;
+ result.kwh_index = P092_DataSettings.IdxkWh3;
+ result.mwh_index = P092_DataSettings.IdxMWh3;
+ break;
+ default:
+ return result;
+ }
+ result.IndexIsValid = 1;
+ return result;
+}
+
+boolean P092_fetch_heatpower(int number, sP092_ReadData* ReadData) {
+ // current power
+ int32_t high;
+ sDLbus_HMindex HMindex = P092_CheckHmRegister(number);
+ if (HMindex.IndexIsValid == 0)
+ return false;
+ uint8_t b1 = DLbus_Data->ByteStream[HMindex.power_index];
+ uint8_t b2 = DLbus_Data->ByteStream[HMindex.power_index + 1];
+ if (P092_DataSettings.CurrentHmBytes > 2) {
+ uint8_t b3 = DLbus_Data->ByteStream[HMindex.power_index + 2];
+ uint8_t b4 = DLbus_Data->ByteStream[HMindex.power_index + 3];
+ high = 0x10000 * b4 + 0x100 * b3 + b2;
+ int low = (b1 * 10) / 0x100;
+ if (!(b4 & 0x80)) // sign positive
+ ReadData->value = (10 * high + low) / 100;
+ else // sign negative
+ ReadData->value = (10 * (high - 0x10000) - low) / 100;
+ }
+ else {
+ high = (b2 << 8) | b1;
+ if ((b2 & 0x80) == 0) // sign positive
+ ReadData->value = high / 10;
+ else // sign negative
+ ReadData->value = (high - 0x10000) / 10;
+ }
+ return true;
+}
+
+boolean P092_fetch_heatmeter(int number, sP092_ReadData* ReadData) {
+ // heat meter
+ int32_t heat_meter;
+ float heat_meter_mwh;
+
+ sDLbus_HMindex HMindex = P092_CheckHmRegister(number);
+ if (HMindex.IndexIsValid == 0)
+ return false;
+ heat_meter = (DLbus_Data->ByteStream[HMindex.kwh_index + 1] << 8) | DLbus_Data->ByteStream[HMindex.kwh_index];
+ heat_meter_mwh = (heat_meter * 0.1) / 1000; // in MWh
+ if (heat_meter_mwh > 1.0) {
+ // in kWh
+ heat_meter = heat_meter_mwh;
+ heat_meter_mwh -= heat_meter;
+ }
+ // MWh
+ heat_meter = (DLbus_Data->ByteStream[HMindex.mwh_index + 1] << 8) | DLbus_Data->ByteStream[HMindex.mwh_index];
+ ReadData->value = heat_meter_mwh + heat_meter;
+ return true;
+}
+
+#endif // USES_P092
diff --git a/src/define_plugin_sets.h b/src/define_plugin_sets.h
index f06951ec7..01bea5b0d 100644
--- a/src/define_plugin_sets.h
+++ b/src/define_plugin_sets.h
@@ -723,6 +723,7 @@ To create/register a plugin, you have to :
#define USES_P063 // TTP229_KeyPad
#define USES_P073 // 7DG
#define USES_P079 // Wemos Motoshield
+ #define USES_P092 // DL-Bus
#endif