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 b745e6545..8979dc359 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 16f9802b9..1e0f12df5 100644 --- a/src/define_plugin_sets.h +++ b/src/define_plugin_sets.h @@ -756,6 +756,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