mirror of https://github.com/arendst/Tasmota.git
275 lines
7.5 KiB
C++
275 lines
7.5 KiB
C++
#include "han_Parser.h"
|
|
#include "Cosem.h"
|
|
|
|
|
|
extern int SML_print(const char *, ...);
|
|
#define han_debug SML_print
|
|
|
|
|
|
Han_Parser::Han_Parser(uint16_t (dp)(uint8_t, uint8_t), uint8_t m, uint8_t *key, uint8_t *auth) {
|
|
dispatch = dp;
|
|
meter = m;
|
|
memmove(encryptionKey, key, 16);
|
|
if (auth) {
|
|
memmove(authenticationKey, auth, 16);
|
|
} else {
|
|
memset(authenticationKey, 0, 16);
|
|
}
|
|
}
|
|
|
|
Han_Parser::~Han_Parser(void) {
|
|
if (hdlcParser) delete hdlcParser;
|
|
if (mbusParser) delete mbusParser;
|
|
if (gbtParser) delete gbtParser;
|
|
if (gcmParser) delete gcmParser;
|
|
if (llcParser) delete llcParser;
|
|
if (dlmsParser) delete dlmsParser;
|
|
if (dsmrParser) delete dsmrParser;
|
|
}
|
|
|
|
int Han_Parser::serial_available(void) {
|
|
return dispatch(meter, 0);
|
|
}
|
|
int Han_Parser::serial_read(void) {
|
|
return dispatch(meter, 1);
|
|
}
|
|
|
|
int16_t Han_Parser::serial_readBytes(uint8_t *buf, uint16_t size) {
|
|
if (size > serial_available()) {
|
|
size = serial_available();
|
|
}
|
|
for (uint16_t cnt = 0; cnt < size; cnt++) {
|
|
buf[cnt] = serial_read();
|
|
}
|
|
return size;
|
|
}
|
|
|
|
bool Han_Parser::readHanPort(uint8_t **out, uint16_t *size, uint8_t flags) {
|
|
|
|
if (!serial_available()) return false;
|
|
|
|
// Before reading, empty serial buffer to increase chance of getting first byte of a data transfer
|
|
if (!serialInit) {
|
|
serial_readBytes(hanBuffer, BUF_SIZE_HAN);
|
|
serialInit = true;
|
|
return false;
|
|
}
|
|
|
|
DataParserContext ctx = {0};
|
|
ctx.flags = flags;
|
|
int pos = DATA_PARSE_INCOMPLETE;
|
|
// For each byte received, check if we have a complete frame we can handle
|
|
while (serial_available() && pos == DATA_PARSE_INCOMPLETE) {
|
|
yield();
|
|
// If buffer was overflowed, reset
|
|
if (len >= BUF_SIZE_HAN) {
|
|
serial_readBytes(hanBuffer, BUF_SIZE_HAN);
|
|
len = 0;
|
|
han_debug(PSTR("Buffer overflow, resetting"));
|
|
return false;
|
|
}
|
|
hanBuffer[len++] = serial_read();
|
|
ctx.length = len;
|
|
pos = unwrapData((uint8_t *) hanBuffer, ctx);
|
|
if(ctx.type > 0 && pos >= 0) {
|
|
if(ctx.type == DATA_TAG_DLMS) {
|
|
han_debug(PSTR("Received valid DLMS at %d"), pos);
|
|
} else if(ctx.type == DATA_TAG_DSMR) {
|
|
han_debug(PSTR("Received valid DSMR at %d"), pos);
|
|
} else {
|
|
// TODO: Move this so that payload is sent to MQTT
|
|
han_debug(PSTR("Unknown tag %02X at pos %d"), ctx.type, pos);
|
|
len = 0;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (pos == DATA_PARSE_INCOMPLETE) {
|
|
return false;
|
|
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
|
|
han_debug(PSTR("Unknown data payload:"));
|
|
len = len + serial_readBytes(hanBuffer + len, BUF_SIZE_HAN - len);
|
|
//debugPrint(hanBuffer, 0, len);
|
|
len = 0;
|
|
return false;
|
|
}
|
|
|
|
if (pos == DATA_PARSE_INTERMEDIATE_SEGMENT) {
|
|
len = 0;
|
|
return false;
|
|
} else if (pos < 0) {
|
|
printHanReadError(pos);
|
|
len += serial_readBytes(hanBuffer + len, BUF_SIZE_HAN - len);
|
|
while (serial_available()) serial_read(); // Make sure it is all empty, in case we overflowed buffer above
|
|
len = 0;
|
|
return false;
|
|
}
|
|
|
|
// Data is valid, clear the rest of the buffer to avoid tainted parsing
|
|
for (int i = pos + ctx.length; i < BUF_SIZE_HAN; i++) {
|
|
hanBuffer[i] = 0x00;
|
|
}
|
|
|
|
//AmsData data;
|
|
char* payload = ((char *) (hanBuffer)) + pos;
|
|
if (ctx.type == DATA_TAG_DLMS) {
|
|
han_debug(PSTR("Using application data:"));
|
|
|
|
//if (Debug.isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length);
|
|
|
|
// Rudimentary detector for L&G proprietary format
|
|
if (payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
|
|
//data = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
|
|
} else {
|
|
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
|
|
//data = IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx);
|
|
}
|
|
} else if(ctx.type == DATA_TAG_DSMR) {
|
|
//data = IEC6205621(payload);
|
|
}
|
|
|
|
*out = hanBuffer + pos;
|
|
*size = ctx.length;
|
|
len = 0;
|
|
return true;
|
|
}
|
|
|
|
|
|
int16_t Han_Parser::unwrapData(uint8_t *buf, DataParserContext &context) {
|
|
int16_t ret = 0;
|
|
bool doRet = false;
|
|
uint16_t end = BUF_SIZE_HAN;
|
|
uint8_t tag = (*buf);
|
|
uint8_t lastTag = DATA_TAG_NONE;
|
|
while (tag != DATA_TAG_NONE) {
|
|
int16_t curLen = context.length;
|
|
int8_t res = 0;
|
|
switch(tag) {
|
|
case DATA_TAG_HDLC:
|
|
if (hdlcParser == NULL) hdlcParser = new HDLCParser();
|
|
res = hdlcParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_MBUS:
|
|
if (mbusParser == NULL) mbusParser = new MBUSParser();
|
|
res = mbusParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_GBT:
|
|
if (gbtParser == NULL) gbtParser = new GBTParser();
|
|
res = gbtParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_GCM:
|
|
if (gcmParser == NULL) gcmParser = new GCMParser(encryptionKey, authenticationKey);
|
|
res = gcmParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_LLC:
|
|
if (llcParser == NULL) llcParser = new LLCParser();
|
|
res = llcParser->parse(buf, context);
|
|
break;
|
|
case DATA_TAG_DLMS:
|
|
if (dlmsParser == NULL) dlmsParser = new DLMSParser();
|
|
res = dlmsParser->parse(buf, context);
|
|
if (res >= 0) doRet = true;
|
|
break;
|
|
case DATA_TAG_DSMR:
|
|
if (dsmrParser == NULL) dsmrParser = new DSMRParser();
|
|
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
|
|
if (res >= 0) doRet = true;
|
|
break;
|
|
default:
|
|
han_debug(PSTR("Ended up in default case while unwrapping...(tag %02X)"), tag);
|
|
return DATA_PARSE_UNKNOWN_DATA;
|
|
}
|
|
lastTag = tag;
|
|
if (res == DATA_PARSE_INCOMPLETE) {
|
|
return res;
|
|
}
|
|
if (context.length > end) return false;
|
|
if (Debug) {
|
|
switch(tag) {
|
|
case DATA_TAG_HDLC:
|
|
han_debug(PSTR("HDLC frame:"));
|
|
break;
|
|
case DATA_TAG_MBUS:
|
|
han_debug(PSTR("MBUS frame:"));
|
|
break;
|
|
case DATA_TAG_GBT:
|
|
han_debug(PSTR("GBT frame:"));
|
|
break;
|
|
case DATA_TAG_GCM:
|
|
han_debug(PSTR("GCM frame:"));
|
|
break;
|
|
case DATA_TAG_LLC:
|
|
han_debug(PSTR("LLC frame:"));
|
|
break;
|
|
case DATA_TAG_DLMS:
|
|
han_debug(PSTR("DLMS frame:"));
|
|
break;
|
|
case DATA_TAG_DSMR:
|
|
han_debug(PSTR("DSMR frame:"));
|
|
break;
|
|
}
|
|
}
|
|
if (res == DATA_PARSE_FINAL_SEGMENT) {
|
|
if (tag == DATA_TAG_MBUS) {
|
|
res = mbusParser->write(buf, context);
|
|
}
|
|
}
|
|
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
buf += res;
|
|
end -= res;
|
|
ret += res;
|
|
|
|
// If we are ready to return, do that
|
|
if (doRet) {
|
|
context.type = tag;
|
|
return ret;
|
|
}
|
|
|
|
// Use start byte of new buffer position as tag for next round in loop
|
|
tag = (*buf);
|
|
}
|
|
han_debug(PSTR("Got to end of unwrap method..."));
|
|
return DATA_PARSE_UNKNOWN_DATA;
|
|
}
|
|
|
|
void Han_Parser::printHanReadError(int16_t pos) {
|
|
switch(pos) {
|
|
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
|
|
han_debug(PSTR("Boundry flag missing"));
|
|
break;
|
|
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
|
|
han_debug(PSTR("Header checksum error"));
|
|
break;
|
|
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
|
|
han_debug(PSTR("Frame checksum error"));
|
|
break;
|
|
case DATA_PARSE_INCOMPLETE:
|
|
han_debug(PSTR("Received frame is incomplete"));
|
|
break;
|
|
case GCM_AUTH_FAILED:
|
|
han_debug(PSTR("Decrypt authentication failed"));
|
|
break;
|
|
case GCM_ENCRYPTION_KEY_FAILED:
|
|
han_debug(PSTR("Setting decryption key failed"));
|
|
break;
|
|
case GCM_DECRYPT_FAILED:
|
|
han_debug(PSTR("Decryption failed"));
|
|
break;
|
|
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
|
han_debug(PSTR("Frame length mismatch"));
|
|
break;
|
|
case DATA_PARSE_INTERMEDIATE_SEGMENT:
|
|
han_debug(PSTR("Intermediate segment received"));
|
|
break;
|
|
case DATA_PARSE_UNKNOWN_DATA:
|
|
han_debug(PSTR("Unknown data format %02X"), hanBuffer[0]);
|
|
break;
|
|
default:
|
|
han_debug(PSTR("Unspecified error while reading data: %d"), pos);
|
|
break;
|
|
}
|
|
}
|