mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-09-12 17:45:49 +00:00
373 lines
11 KiB
C++
373 lines
11 KiB
C++
#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;
|
|
}
|