merged with release mega-20200305

merged with release mega-20200305
This commit is contained in:
uwekaditz
2020-03-09 19:59:56 +01:00
parent caf3b46bf7
commit bec007abb7
30 changed files with 1794 additions and 2 deletions
+36
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+50
View File
@@ -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 <http://www.ta.co.at/>`_.
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
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+76
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+80
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+71
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

+76
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+1
View File
@@ -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
@@ -8,4 +8,15 @@
.. |P090_shortinfo| replace:: `.`
.. |P090_maintainer| replace:: `TD-er`
.. |P090_compileinfo| replace:: `.`
.. |P090_usedlibraries| replace:: `.`
.. |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:: `.`
+372
View File
@@ -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;
}
+65
View File
@@ -0,0 +1,65 @@
#ifndef DLBus_H
#define DLBus_H
#include <Arduino.h>
/*********************************************************************************************\
DLBus subs to get values from the receiving bitstream
\*********************************************************************************************/
// one data frame has <P092_DataSettings.DataBytes> 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
+6
View File
@@ -0,0 +1,6 @@
DLBus KEYWORD1
attachDLBusInterrupt KEYWORD2
StartReceiving KEYWORD2
CheckTimings KEYWORD2
Processing KEYWORD2
CheckCRC KEYWORD2
+1 -1
View File
@@ -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"
+947
View File
@@ -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 <DLBus.h>
#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<long>(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
+1
View File
@@ -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