espHome-NBS-files/external_components/hoermann_door/hciemulator.cpp

493 lines
14 KiB
C++

#include "hciemulator.h"
#include "Arduino.h"
#define CHECKCHANGEDSET(Target, Value, Flag) \
if ((Target) != (Value)) \
{ \
Target = Value; \
Flag = true; \
}
int hciloglevel = LL_DEBUG;
#define SOFTSERIAL 1
#ifdef SOFTSERIAL
#define Log(Level, Message) LogCore(Level, Message)
#define Log3(Level, Message, Buffer, Len) LogCore(Level, Message, Buffer, Len)
// LOGLEVEL
void LogCore(int Level, const char *msg, const unsigned char *data = NULL, size_t datalen = 0)
{
if (Level > hciloglevel)
{
return;
}
if (data != NULL && datalen > 0)
{
String newmsg(msg);
char str[4];
for (size_t i = 0; i < datalen; i++)
{
snprintf(str, sizeof(str), "%02x ", data[i]);
newmsg += str;
}
Serial.println(newmsg);
}
else
{
Serial.println(msg);
}
}
#else
#define Log(Level, Message)
#define Log3(Level, Message, Buffer, Len)
#endif
int HCIEmulator::getLogLevel()
{
return hciloglevel;
}
void HCIEmulator::setLogLevel(int level)
{
hciloglevel = level;
}
// modbus crc calculation borrowed from:
// https://github.com/yaacov/ArduinoModbusSlave
#define MODBUS_CRC_LENGTH 2
#define readCRC(arr, length) word(arr[(length - MODBUS_CRC_LENGTH) + 1], arr[length - MODBUS_CRC_LENGTH])
#define readUInt16(arr, index) word(arr[index], arr[index + 1])
/**
* Calculate the CRC of the passed byte array from zero up to the passed length.
*
* @param buffer The byte array containing the data.
* @param length The length of the byte array.
*
* @return The calculated CRC as an unsigned 16 bit integer.
*
* Calculate and add the CRC.
* uint16_t crc = Modbus::calculateCRC(_responseBuffer, _responseBufferLength - MODBUS_CRC_LENGTH);
* _responseBuffer[_responseBufferLength - MODBUS_CRC_LENGTH] = crc & 0xFF;
* _responseBuffer[(_responseBufferLength - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
*
*
* #define MODBUS_FRAME_SIZE 4
* #define MODBUS_CRC_LENGTH 2
* uint16_t crc = readCRC(_requestBuffer, _requestBufferLength);
* #define readUInt16(arr, index) word(arr[index], arr[index + 1])
* #define readCRC(arr, length) word(arr[(length - MODBUS_CRC_LENGTH) + 1], arr[length - MODBUS_CRC_LENGTH])
*/
uint16_t calculateCRC(uint8_t *buffer, int length)
{
int i, j;
uint16_t crc = 0xFFFF;
uint16_t tmp;
// Calculate the CRC.
for (i = 0; i < length; i++)
{
crc = crc ^ buffer[i];
for (j = 0; j < 8; j++)
{
tmp = crc & 0x0001;
crc = crc >> 1;
if (tmp)
{
crc = crc ^ 0xA001;
}
}
}
return crc;
}
HCIEmulator::HCIEmulator()
{
m_state.valid = false;
m_statemachine = WAITING;
m_rxlen = m_txlen = 0;
m_recvTime = m_lastStateTime = 0;
m_skipFrame = false;
m_port = &Serial2;
m_statusCallback = NULL;
setLogLevel(DEFAULTLOGLEVEL);
};
#define TX_ON 25
void HCIEmulator::poll()
{
if (m_port == NULL)
return;
// receive Data
if (m_port->available() > 0)
{
// Serial.println("got data");
m_rxlen += m_port->readBytes((char *)(m_rxbuffer + m_rxlen), _min((int)(255 - m_rxlen), m_port->available()));
if (m_rxlen > 254)
{
Log(LL_ERROR, "RX Bufferoverflow, skip next Frame");
Log3(LL_DEBUG, "Buffer Data: ", m_rxbuffer, m_rxlen);
m_rxlen = 0;
m_skipFrame = true;
}
m_recvTime = micros();
}
// Serial.printf("Data % x\n", m_txbuffer);
// check frame, process frame
if (m_rxlen > 0 && (micros() - m_recvTime > T3_5))
{
// Serial.printf("Act on it % x\n", m_txbuffer);
// check last action timeout -> reset > then 2sec
if (m_statemachine != WAITING && m_lastStateTime + 2000 < millis())
{
m_statemachine = WAITING;
}
if (!m_skipFrame)
{
processFrame();
// send response
if (m_txlen > 0)
{
// fix crc
uint16_t crc = calculateCRC(m_txbuffer, m_txlen - MODBUS_CRC_LENGTH);
m_txbuffer[m_txlen - MODBUS_CRC_LENGTH] = crc & 0xFF;
m_txbuffer[(m_txlen - MODBUS_CRC_LENGTH) + 1] = crc >> 8;
// send data
m_lastSendTime = micros() - m_recvTime;
// Log(LL_DEBUG, ("ST:"+String(m_lastSendTime)).c_str());
digitalWrite(TX_ON, HIGH);
// Log3(LL_DEBUG, "write data: ");
m_port->write(m_txbuffer, m_txlen);
Log3(LL_DEBUG, "Response: ", m_txbuffer, m_txlen);
delayMicroseconds(m_txlen * 9 * 22); // 8 bits + par * Bittime 18 micros on 57600 bauds
digitalWrite(TX_ON, LOW);
m_txlen = 0;
}
}
else
{
Serial.println("skipped frame");
}
m_skipFrame = false;
m_rxlen = 0;
}
}
void HCIEmulator::processFrame()
{
m_txlen = 0; // clear send buffer
if (m_rxlen < 5)
{
Log(LL_ERROR, "Frame skipped, invalid frame len");
Log3(LL_ERROR, "Data:", m_rxbuffer, m_rxlen);
return;
}
// check device id, pass only device id 2 and 0 (broadcast)
if (m_rxbuffer[0] != BROADCASTID && m_rxbuffer[0] != DEVICEID)
{
Log(LL_DEBUG, "Frame skipped, unsupported device id");
Log3(LL_DEBUG, "Data:", m_rxbuffer, m_rxlen);
return;
}
// check crc
uint16_t crc = readCRC(m_rxbuffer, m_rxlen);
if (crc != calculateCRC(m_rxbuffer, m_rxlen - MODBUS_CRC_LENGTH))
{
Log3(LL_ERROR, "Frame skipped, wrong crc", m_rxbuffer, m_rxlen);
return;
}
Log3(LL_DEBUG, "Incomming Data: ", m_rxbuffer, m_rxlen);
// dispatch modbus function
switch (m_rxbuffer[1])
{
case 0x10:
{ // Write Multiple registers
if (m_rxlen == 0x1b && m_rxbuffer[0] == BROADCASTID)
{
processBroadcastStatusFrame();
return;
}
break;
}
case 0x17:
{ // Read/Write Multiple registers
if (m_rxbuffer[0] == DEVICEID)
{
switch (m_rxlen)
{
case 0x11:
{
processDeviceStatusFrame();
return;
}
case 0x13:
processDeviceBusScanFrame();
return;
;
}
}
break;
}
}
Log3(LL_ERROR, "Frame skipped, unexpected data: ", m_rxbuffer, m_rxlen);
}
const unsigned char ResponseTemplate_Fcn17_Cmd03_L08[] = {0x02, 0x17, 0x10, 0x3E, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1B};
const unsigned char ResponseTemplate_Fcn17_Cmd04_L02[] = {0x02, 0x17, 0x04, 0x0F, 0x00, 0x04, 0xFD, 0x0A, 0x72};
void HCIEmulator::processDeviceStatusFrame()
{
if (m_rxlen == 0x11)
{
unsigned char counter = m_rxbuffer[11];
unsigned char cmd = m_rxbuffer[12];
if (m_rxbuffer[5] == 0x08)
{
// expose internal state
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
// 0011: 02 17 9C B9 00 08 9C 41 00 02 04 3E 03 00 00 EB CC
// res=> 02 17 10 3E 00 03 01 00 00 00 00 00 00 00 00 00 00 00 00 74 1B
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd03_L08, sizeof(ResponseTemplate_Fcn17_Cmd03_L08));
m_txbuffer[0] = m_rxbuffer[0];
m_txbuffer[3] = counter;
m_txbuffer[5] = cmd;
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd03_L08);
switch (m_statemachine)
{
// open Door
case STARTOPENDOOR:
m_txbuffer[7] = 0x02;
m_txbuffer[8] = 0x10;
m_statemachine = STARTOPENDOOR_RELEASE;
m_lastStateTime = millis();
break;
case STARTOPENDOOR_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[8] = 0x10;
m_statemachine = WAITING;
}
break;
// close Door
case STARTCLOSEDOOR:
m_txbuffer[7] = 0x02;
m_txbuffer[8] = 0x20;
m_statemachine = STARTCLOSEDOOR_RELEASE;
m_lastStateTime = millis();
break;
case STARTCLOSEDOOR_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[8] = 0x20;
m_statemachine = WAITING;
}
break;
// stop Door
case STARTSTOPDOOR:
m_txbuffer[7] = 0x02;
m_txbuffer[8] = 0x40;
m_statemachine = STARTSTOPDOOR_RELEASE;
m_lastStateTime = millis();
break;
case STARTSTOPDOOR_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[8] = 0x40;
m_statemachine = WAITING;
}
break;
// Ventilation
case STARTVENTPOSITION:
m_txbuffer[7] = 0x02;
m_txbuffer[9] = 0x40;
m_statemachine = STARTVENTPOSITION_RELEASE;
m_lastStateTime = millis();
break;
case STARTVENTPOSITION_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[9] = 0x40;
m_statemachine = WAITING;
}
break;
// Half Position
case STARTOPENDOORHALF:
m_txbuffer[7] = 0x02;
m_txbuffer[9] = 0x04;
m_statemachine = STARTOPENDOORHALF_RELEASE;
m_lastStateTime = millis();
break;
case STARTOPENDOORHALF_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
{
m_txbuffer[7] = 0x01;
m_txbuffer[9] = 0x04;
m_statemachine = WAITING;
}
break;
// Toggle Lamp
case STARTTOGGLELAMP:
m_txbuffer[7] = 0x10;
m_txbuffer[9] = 0x02;
m_statemachine = STARTTOGGLELAMP_RELEASE;
m_lastStateTime = millis();
break;
case STARTTOGGLELAMP_RELEASE:
if (m_lastStateTime + SIMULATEKEYPRESSDELAYMS < millis())
{
m_txbuffer[7] = 0x08;
m_txbuffer[9] = 0x02;
m_statemachine = WAITING;
}
break;
case WAITING:
break;
}
return;
}
else if (m_rxbuffer[5] == 0x02)
{
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
// 0011: 02 17 9C B9 00 02 9C 41 00 02 04 0F 04 17 00 7B 21
// res=> 02 17 04 0F 00 04 FD 0A 72
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd04_L02, sizeof(ResponseTemplate_Fcn17_Cmd04_L02));
m_txbuffer[0] = m_rxbuffer[0];
m_txbuffer[3] = counter;
m_txbuffer[5] = cmd;
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd04_L02);
return;
}
}
Log3(LL_ERROR, "Frame skipped, unexpected data: ", m_rxbuffer, m_rxlen);
}
const unsigned char ResponseTemplate_Fcn17_Cmd02_L05[] = {0x02, 0x17, 0x0a, 0x00, 0x00, 0x02, 0x05, 0x04, 0x30, 0x10, 0xff, 0xa8, 0x45, 0x0e, 0xdf};
void HCIEmulator::processDeviceBusScanFrame()
{
// 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
// 0013: 02 17 9C B9 00 05 9C 41 00 03 06 00 02 00 00 01 02 f8 35
// res=> 02 17 0a 00 00 02 05 04 30 10 ff a8 45 0e df
unsigned char counter = m_rxbuffer[11];
unsigned char cmd = m_rxbuffer[12];
memcpy(m_txbuffer, ResponseTemplate_Fcn17_Cmd02_L05, sizeof(ResponseTemplate_Fcn17_Cmd02_L05));
m_txbuffer[0] = m_rxbuffer[0];
m_txbuffer[3] = counter;
m_txbuffer[5] = cmd;
m_txlen = sizeof(ResponseTemplate_Fcn17_Cmd02_L05);
Log(LL_INFO, "Busscan received");
}
void HCIEmulator::processBroadcastStatusFrame()
{
// 001B: 00 10 9D 31 00 09 12 64 00 00 00 40 60 00 00 00 00 00 00 00 00 00 01 00 00 CA 22
bool hasChanged = false;
CHECKCHANGEDSET(m_state.lampOn, m_rxbuffer[20] == 0x14, hasChanged);
CHECKCHANGEDSET(m_state.doorCurrentPosition, m_rxbuffer[10], hasChanged);
CHECKCHANGEDSET(m_state.doorTargetPosition, m_rxbuffer[9], hasChanged);
CHECKCHANGEDSET(m_state.doorState, m_rxbuffer[11], hasChanged);
CHECKCHANGEDSET(m_state.reserved, m_rxbuffer[17], hasChanged);
CHECKCHANGEDSET(m_state.valid, true, hasChanged);
if (hasChanged)
{
Log3(LL_INFO, "New State: ", m_rxbuffer, m_rxlen);
if (m_statusCallback != NULL)
{
m_statusCallback(m_state);
}
}
}
void HCIEmulator::openDoor()
{
if (m_statemachine != WAITING)
{
return;
}
m_lastStateTime = millis();
m_statemachine = STARTOPENDOOR;
}
void HCIEmulator::openDoorHalf()
{
if (m_statemachine != WAITING)
{
return;
}
m_lastStateTime = millis();
m_statemachine = STARTOPENDOORHALF;
}
void HCIEmulator::closeDoor()
{
if (m_statemachine != WAITING)
{
return;
}
m_lastStateTime = millis();
m_statemachine = STARTCLOSEDOOR;
}
void HCIEmulator::stopDoor()
{
if (m_statemachine != WAITING)
{
return;
}
m_lastStateTime = millis();
m_statemachine = STARTSTOPDOOR;
}
void HCIEmulator::toggleLamp()
{
if (m_statemachine != WAITING)
{
return;
}
m_lastStateTime = millis();
m_statemachine = STARTTOGGLELAMP;
}
void HCIEmulator::ventilationPosition()
{
if (m_statemachine != WAITING)
{
return;
}
m_lastStateTime = millis();
m_statemachine = STARTVENTPOSITION;
}
void HCIEmulator::onStatusChanged(callback_function_t handler)
{
m_statusCallback = handler;
}