Files
ESPEasy/lib/DLBus/DLBus.cpp
T
uwekaditz bec007abb7 merged with release mega-20200305
merged with release mega-20200305
2020-03-09 19:59:56 +01:00

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;
}