diff --git a/lib/lib_div/ams/Cosem.cpp b/lib/lib_div/ams/Cosem.cpp new file mode 100644 index 000000000..b53cc74b6 --- /dev/null +++ b/lib/lib_div/ams/Cosem.cpp @@ -0,0 +1,25 @@ +#include "Cosem.h" +#include "lwip/def.h" +#include "TimeLib.h" + + +time_t decodeCosemDateTime(CosemDateTime timestamp) { + tmElements_t tm; + uint16_t year = ntohs(timestamp.year); + if(year < 1970) return 0; + tm.Year = year - 1970; + tm.Month = timestamp.month; + tm.Day = timestamp.dayOfMonth; + tm.Hour = timestamp.hour; + tm.Minute = timestamp.minute; + tm.Second = timestamp.second; + + //Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status); + + time_t time = makeTime(tm); + int16_t deviation = ntohs(timestamp.deviation); + if(deviation >= -720 && deviation <= 720) { + time -= deviation * 60; + } + return time; +} diff --git a/lib/lib_div/ams/Cosem.h b/lib/lib_div/ams/Cosem.h new file mode 100644 index 000000000..e218cd7eb --- /dev/null +++ b/lib/lib_div/ams/Cosem.h @@ -0,0 +1,92 @@ +#ifndef _COSEM_H +#define _COSEM_H + +#include "lwip/def.h" + +// Blue book, Table 2 +enum CosemType { + CosemTypeNull = 0x00, + CosemTypeArray = 0x01, + CosemTypeStructure = 0x02, + CosemTypeOctetString = 0x09, + CosemTypeString = 0x0A, + CosemTypeDLongSigned = 0x05, + CosemTypeDLongUnsigned = 0x06, + CosemTypeLongSigned = 0x10, + CosemTypeLongUnsigned = 0x12, + CosemTypeLong64Signed = 0x14, + CosemTypeLong64Unsigned = 0x15, + CosemTypeDateTime = 0x19 +}; + +struct CosemBasic { + uint8_t type; + uint8_t length; +} __attribute__((packed)); + +struct CosemString { + uint8_t type; + uint8_t length; + uint8_t data[]; +} __attribute__((packed)); + +struct CosemLongSigned { + uint8_t type; + int16_t data; +} __attribute__((packed)); + +struct CosemLongUnsigned { + uint8_t type; + uint16_t data; +} __attribute__((packed)); + +struct CosemDLongSigned { + uint8_t type; + int32_t data; +} __attribute__((packed)); + +struct CosemDLongUnsigned { + uint8_t type; + uint32_t data; +} __attribute__((packed)); + +struct CosemLong64Signed { + uint8_t type; + int64_t data; +} __attribute__((packed)); + +struct CosemLong64Unsigned { + uint8_t type; + uint64_t data; +} __attribute__((packed)); + +struct CosemDateTime { + uint8_t type; + uint16_t year; + uint8_t month; + uint8_t dayOfMonth; + uint8_t dayOfWeek; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t hundredths; + int16_t deviation; + uint8_t status; +} __attribute__((packed)); + +typedef union { + struct CosemBasic base; + struct CosemString str; + struct CosemString oct; + struct CosemLongSigned ls; + struct CosemLongUnsigned lu; + struct CosemDLongSigned dls; + struct CosemDLongUnsigned dlu; + struct CosemLong64Signed l64s; + struct CosemLong64Unsigned l64u; + struct CosemDateTime dt; +} CosemData; + +time_t decodeCosemDateTime(CosemDateTime timestamp); + +#endif diff --git a/lib/lib_div/ams/DataParser.h b/lib/lib_div/ams/DataParser.h new file mode 100644 index 000000000..6aa92faec --- /dev/null +++ b/lib/lib_div/ams/DataParser.h @@ -0,0 +1,31 @@ +#ifndef _DATAPASERSER_H +#define _DATAPASERSER_H + +#define DATA_TAG_NONE 0x00 +#define DATA_TAG_AUTO 0x01 +#define DATA_TAG_HDLC 0x7E +#define DATA_TAG_LLC 0xE6 +#define DATA_TAG_DLMS 0x0F +#define DATA_TAG_DSMR 0x2F +#define DATA_TAG_MBUS 0x68 +#define DATA_TAG_GBT 0xE0 +#define DATA_TAG_GCM 0xDB + +#define DATA_PARSE_OK 0 +#define DATA_PARSE_FAIL -1 +#define DATA_PARSE_INCOMPLETE -2 +#define DATA_PARSE_BOUNDRY_FLAG_MISSING -3 +#define DATA_PARSE_HEADER_CHECKSUM_ERROR -4 +#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5 +#define DATA_PARSE_INTERMEDIATE_SEGMENT -6 +#define DATA_PARSE_FINAL_SEGMENT -7 +#define DATA_PARSE_UNKNOWN_DATA -9 + +struct DataParserContext { + uint8_t type; + uint16_t length; + time_t timestamp; + uint8_t system_title[8]; +}; + +#endif diff --git a/lib/lib_div/ams/DataParsers.h b/lib/lib_div/ams/DataParsers.h new file mode 100644 index 000000000..5355784e8 --- /dev/null +++ b/lib/lib_div/ams/DataParsers.h @@ -0,0 +1,13 @@ +#ifndef _DATAPASERSERS_H +#define _DATAPASERSERS_H + +#include "HdlcParser.h" +#include "DlmsParser.h" +#include "DsmrParser.h" +#include "MbusParser.h" +#include "GbtParser.h" +#include "GcmParser.h" +#include "LlcParser.h" + +#endif + diff --git a/lib/lib_div/ams/DlmsParser.cpp b/lib/lib_div/ams/DlmsParser.cpp new file mode 100644 index 000000000..f0ed63e2b --- /dev/null +++ b/lib/lib_div/ams/DlmsParser.cpp @@ -0,0 +1,38 @@ +#include "DlmsParser.h" +#include "Cosem.h" + +int8_t DLMSParser::parse(uint8_t *buf, DataParserContext &ctx) { + if(ctx.length < 6) return DATA_PARSE_INCOMPLETE; + + uint8_t* ptr = buf+1; + ptr += 4; // Skip invoke ID and priority + + CosemData* item = (CosemData*) ptr; + if(item->base.type == CosemTypeOctetString) { + if(item->base.length == 0x0C) { + CosemDateTime* dateTime = (CosemDateTime*) (ptr+1); + ctx.timestamp = decodeCosemDateTime(*dateTime); + } + uint8_t len = 5+14; + ctx.length -= len; + return len; + } else if(item->base.type == CosemTypeNull) { + ctx.timestamp = 0; + uint8_t len = 5+1; + ctx.length -= len; + return len; + } else if(item->base.type == CosemTypeDateTime) { + CosemDateTime* dateTime = (CosemDateTime*) (ptr); + ctx.timestamp = decodeCosemDateTime(*dateTime); + uint8_t len = 5+13; + ctx.length -= len; + return len; + } else if(item->base.type == 0x0C) { // Kamstrup bug... + CosemDateTime* dateTime = (CosemDateTime*) (ptr); + ctx.timestamp = decodeCosemDateTime(*dateTime); + uint8_t len = 5+13; + ctx.length -= len; + return len; + } + return DATA_PARSE_UNKNOWN_DATA; +} diff --git a/lib/lib_div/ams/DlmsParser.h b/lib/lib_div/ams/DlmsParser.h new file mode 100644 index 000000000..200f0fe91 --- /dev/null +++ b/lib/lib_div/ams/DlmsParser.h @@ -0,0 +1,12 @@ +#ifndef _DLMSPARSER_H +#define _DLMSPARSER_H + +#include "Arduino.h" +#include "DataParser.h" + +class DLMSParser { +public: + int8_t parse(uint8_t *buf, DataParserContext &ctx); +}; + +#endif diff --git a/lib/lib_div/ams/DsmrParser.cpp b/lib/lib_div/ams/DsmrParser.cpp new file mode 100644 index 000000000..94ab1c42e --- /dev/null +++ b/lib/lib_div/ams/DsmrParser.cpp @@ -0,0 +1,29 @@ +#include "DsmrParser.h" +#include "crc.h" +#include "hexutils.h" +#include "lwip/def.h" + +int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) { + uint16_t crcPos = 0; + bool reachedEnd = verified; + uint8_t lastByte = 0x00; + for(int pos = 0; pos < ctx.length; pos++) { + uint8_t b = *(buf+pos); + if(pos == 0 && b != '/') return DATA_PARSE_BOUNDRY_FLAG_MISSING; + if(pos > 0 && b == '!' && lastByte == '\n') crcPos = pos+1; + if(crcPos > 0 && b == '\n') reachedEnd = true; + lastByte = b; + } + if(!reachedEnd) return DATA_PARSE_INCOMPLETE; + buf[ctx.length+1] = '\0'; + if(crcPos > 0) { + uint16_t crc_calc = AMS_crc16(buf, crcPos); + uint16_t crc = 0x0000; + AMS_fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2); + crc = ntohs(crc); + + if(crc != crc_calc) + return DATA_PARSE_FOOTER_CHECKSUM_ERROR; + } + return DATA_PARSE_OK; +} diff --git a/lib/lib_div/ams/DsmrParser.h b/lib/lib_div/ams/DsmrParser.h new file mode 100644 index 000000000..7d476de9f --- /dev/null +++ b/lib/lib_div/ams/DsmrParser.h @@ -0,0 +1,13 @@ +#ifndef _DSMRPARSER_H +#define _DSMRPARSER_H + +#include "Arduino.h" +#include "DataParser.h" + +class DSMRParser { +public: + int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified); +private: +}; + +#endif diff --git a/lib/lib_div/ams/GbtParser.cpp b/lib/lib_div/ams/GbtParser.cpp new file mode 100644 index 000000000..34e366a3b --- /dev/null +++ b/lib/lib_div/ams/GbtParser.cpp @@ -0,0 +1,36 @@ +#include "GbtParser.h" +#include "lwip/def.h" + +GBTParser::~GBTParser(void) { + if (buf) free(buf); +} + +int8_t GBTParser::parse(uint8_t *d, DataParserContext &ctx) { + GBTHeader* h = (GBTHeader*) (d); + uint16_t sequence = ntohs(h->sequence); + + if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING; + + if(sequence == 1) { + if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ? + pos = 0; + } else if(lastSequenceNumber != sequence-1) { + return DATA_PARSE_FAIL; + } + + if(buf == NULL) return DATA_PARSE_FAIL; + + uint8_t* ptr = (uint8_t*) &h[1]; + memcpy(buf + pos, ptr, h->size); + pos += h->size; + lastSequenceNumber = sequence; + + if((h->control & 0x80) == 0x00) { + return DATA_PARSE_INTERMEDIATE_SEGMENT; + } else { + memcpy((uint8_t *) d, buf, pos); + } + ctx.length = pos; + return DATA_PARSE_OK; + +} diff --git a/lib/lib_div/ams/GbtParser.h b/lib/lib_div/ams/GbtParser.h new file mode 100644 index 000000000..d184895db --- /dev/null +++ b/lib/lib_div/ams/GbtParser.h @@ -0,0 +1,27 @@ +#ifndef _GBTPARSER_H +#define _GBTPARSER_H + +#include "Arduino.h" +#include "DataParser.h" + +#define GBT_TAG 0xE0 + +typedef struct GBTHeader { + uint8_t flag; + uint8_t control; + uint16_t sequence; + uint16_t sequenceAck; + uint8_t size; +} __attribute__((packed)) GBTHeader; + +class GBTParser { +public: + int8_t parse(uint8_t *buf, DataParserContext &ctx); + ~GBTParser(void); +private: + uint8_t lastSequenceNumber = 0; + uint16_t pos = 0; + uint8_t *buf = NULL; +}; + +#endif diff --git a/lib/lib_div/ams/GcmParser.cpp b/lib/lib_div/ams/GcmParser.cpp new file mode 100644 index 000000000..cbdf130fe --- /dev/null +++ b/lib/lib_div/ams/GcmParser.cpp @@ -0,0 +1,104 @@ + +#include "GcmParser.h" +#include "tasmota_options.h" + +#ifdef USE_TLS +#include "lwip/def.h" +#include + +GCMParser::GCMParser(uint8_t *encryption_key, uint8_t *authentication_key) { + memcpy(this->encryption_key, encryption_key, 16); + memcpy(this->authentication_key, authentication_key, 16); + use_auth = 0; + for (uint16_t cnt = 0; cnt < 16; cnt++) { + if (authentication_key[cnt]) { + use_auth |= 1; + } + } +} + +int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) { + if(ctx.length < 12) return DATA_PARSE_INCOMPLETE; + + uint8_t* ptr = (uint8_t*) d; + if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING; + ptr++; + // Encrypted APDU + // http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html + + uint8_t systemTitleLength = *ptr; + ptr++; + + uint8_t initialization_vector[12]; + memcpy(ctx.system_title, ptr, systemTitleLength); + memcpy(initialization_vector, ctx.system_title, systemTitleLength); + + int len = 0; + int headersize = 2 + systemTitleLength; + ptr += systemTitleLength; + if(((*ptr) & 0xFF) == 0x81) { + ptr++; + len = *ptr; + // 1-byte payload length + ptr++; + headersize += 2; + } else if(((*ptr) & 0xFF) == 0x82) { + GCMSizeDef* h = (GCMSizeDef*) ptr; + + // 2-byte payload length + len = (ntohs(h->format) & 0xFFFF); + + ptr += 3; + headersize += 3; + } else if(((*ptr) & 0xFF) == 0x4f) { + // ???????? single frame did only decode with this compare + ptr++; + headersize++; + } + if(len + headersize > ctx.length) + return DATA_PARSE_INCOMPLETE; + + uint8_t additional_authenticated_data[17]; + memcpy(additional_authenticated_data, ptr, 1); + + // Security tag + uint8_t sec = *ptr; + ptr++; + headersize++; + + // Frame counter + memcpy(initialization_vector + 8, ptr, 4); + ptr += 4; + headersize += 4; + + int footersize = 0; + + // Authentication enabled + uint8_t authentication_tag[12]; + uint8_t authkeylen = 0, aadlen = 0; + if((sec & 0x10) == 0x10) { + authkeylen = 12; + aadlen = 17; + footersize += authkeylen; + memcpy(additional_authenticated_data + 1, authentication_key, 16); + memcpy(authentication_tag, ptr + len - footersize - 5, authkeylen); + } + + br_gcm_context gcm_ctx; + br_aes_small_ctr_keys ctr_ctx; + br_aes_small_ctr_init(&ctr_ctx, encryption_key, 16); + br_gcm_init(&gcm_ctx, &ctr_ctx.vtable, &br_ghash_ctmul32); + br_gcm_reset(&gcm_ctx, initialization_vector, 12); + if (use_auth && authkeylen > 0) { + br_gcm_aad_inject(&gcm_ctx, additional_authenticated_data, aadlen); + } + br_gcm_flip(&gcm_ctx); + br_gcm_run(&gcm_ctx, 0, ptr , ctx.length - headersize); + if (use_auth && authkeylen > 0 && br_gcm_check_tag_trunc(&gcm_ctx, authentication_tag, authkeylen) != 1) { + return GCM_AUTH_FAILED; + } + + ctx.length -= footersize + headersize; + return ptr - d; +} +#endif // USE_TLS diff --git a/lib/lib_div/ams/GcmParser.h b/lib/lib_div/ams/GcmParser.h new file mode 100644 index 000000000..71ec0fe95 --- /dev/null +++ b/lib/lib_div/ams/GcmParser.h @@ -0,0 +1,28 @@ +#ifndef _GCMPARSER_H +#define _GCMPARSER_H + +#include "Arduino.h" +#include "DataParser.h" + +#define GCM_TAG 0xDB +#define GCM_AUTH_FAILED -51 +#define GCM_DECRYPT_FAILED -52 +#define GCM_ENCRYPTION_KEY_FAILED -53 + +typedef struct GCMSizeDef { + uint8_t flag; + uint16_t format; +} __attribute__((packed)) GCMSizeDef; + + +class GCMParser { +public: + GCMParser(uint8_t *encryption_key, uint8_t *authentication_key); + int8_t parse(uint8_t *buf, DataParserContext &ctx); +private: + uint8_t encryption_key[16]; + uint8_t authentication_key[16]; + uint8_t use_auth = 0; +}; + +#endif diff --git a/lib/lib_div/ams/HdlcParser.cpp b/lib/lib_div/ams/HdlcParser.cpp new file mode 100644 index 000000000..9e6faad74 --- /dev/null +++ b/lib/lib_div/ams/HdlcParser.cpp @@ -0,0 +1,56 @@ +#include "HdlcParser.h" +#include "lwip/def.h" +#include "crc.h" + +int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) { + int len; + + uint8_t* ptr; + if(ctx.length < 3) + return DATA_PARSE_INCOMPLETE; + + HDLCHeader* h = (HDLCHeader*) d; + ptr = (uint8_t*) &h[1]; + + // Frame format type 3 + if((h->format & 0xF0) == 0xA0) { + // Length field (11 lsb of format) + len = (ntohs(h->format) & 0x7FF) + 2; + if(len > ctx.length) + return DATA_PARSE_INCOMPLETE; + + HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f); + + // First and last byte should be HDLC_FLAG + if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG) + return DATA_PARSE_BOUNDRY_FLAG_MISSING; + + // Verify FCS + if(ntohs(f->fcs) != AMS_crc16_x25(d + 1, len - sizeof *f - 1)) + return DATA_PARSE_FOOTER_CHECKSUM_ERROR; + + // Skip destination address, LSB marks last byte + while(((*ptr) & 0x01) == 0x00) { + ptr++; + } + ptr++; + + // Skip source address, LSB marks last byte + while(((*ptr) & 0x01) == 0x00) { + ptr++; + } + ptr++; + + HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr); + + // Verify HCS + if(ntohs(t3->hcs) != AMS_crc16_x25(d + 1, ptr-d)) + return DATA_PARSE_HEADER_CHECKSUM_ERROR; + ptr += 3; + + // Exclude all of header and 3 byte footer + ctx.length -= ptr-d+3; + return ptr-d; + } + return DATA_PARSE_UNKNOWN_DATA; +} diff --git a/lib/lib_div/ams/HdlcParser.h b/lib/lib_div/ams/HdlcParser.h new file mode 100644 index 000000000..7091cfbda --- /dev/null +++ b/lib/lib_div/ams/HdlcParser.h @@ -0,0 +1,29 @@ +#ifndef _HDLCPARSER_H +#define _HDLCPARSER_H + +#include "Arduino.h" +#include "DataParser.h" + +#define HDLC_FLAG 0x7E + +typedef struct HDLCHeader { + uint8_t flag; + uint16_t format; +} __attribute__((packed)) HDLCHeader; + +typedef struct HDLCFooter { + uint16_t fcs; + uint8_t flag; +} __attribute__((packed)) HDLCFooter; + +typedef struct HDLC3CtrlHcs { + uint8_t control; + uint16_t hcs; +} __attribute__((packed)) HDLC3CtrlHcs; + +class HDLCParser { +public: + int8_t parse(uint8_t *buf, DataParserContext &ctx); +}; + +#endif diff --git a/lib/lib_div/ams/LlcParser.cpp b/lib/lib_div/ams/LlcParser.cpp new file mode 100644 index 000000000..c6d41a564 --- /dev/null +++ b/lib/lib_div/ams/LlcParser.cpp @@ -0,0 +1,6 @@ +#include "LlcParser.h" + +int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) { + ctx.length -= 3; + return 3; +} \ No newline at end of file diff --git a/lib/lib_div/ams/LlcParser.h b/lib/lib_div/ams/LlcParser.h new file mode 100644 index 000000000..3be931092 --- /dev/null +++ b/lib/lib_div/ams/LlcParser.h @@ -0,0 +1,18 @@ +#ifndef _LLCPARSER_H +#define _LLCPARSER_H + +#include "Arduino.h" +#include "DataParser.h" + +typedef struct LLCHeader { + uint8_t dst; + uint8_t src; + uint8_t control; +} __attribute__((packed)) LLCHeader; + +class LLCParser { +public: + int8_t parse(uint8_t *buf, DataParserContext &ctx); +}; + +#endif diff --git a/lib/lib_div/ams/MbusParser.cpp b/lib/lib_div/ams/MbusParser.cpp new file mode 100644 index 000000000..e9c93a5a9 --- /dev/null +++ b/lib/lib_div/ams/MbusParser.cpp @@ -0,0 +1,91 @@ +#include "MbusParser.h" + + +MBUSParser::~MBUSParser(void) { + if (buf) free(buf); +} +int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) { + int len; + int headersize = 3; + int footersize = 1; + + uint8_t* ptr; + + // https://m-bus.com/documentation-wired/06-application-layer + if(ctx.length < 4) + return DATA_PARSE_INCOMPLETE; + + MbusHeader* mh = (MbusHeader*) d; + if(mh->flag1 != MBUS_START || mh->flag2 != MBUS_START) + return DATA_PARSE_BOUNDRY_FLAG_MISSING; + + // First two bytes is 1-byte length value repeated. Only used for last segment + if(mh->len1 != mh->len2) + return MBUS_FRAME_LENGTH_NOT_EQUAL; + len = mh->len1; + ptr = (uint8_t*) &mh[1]; + headersize = 4; + footersize = 2; + + if(len == 0x00) + len = ctx.length - headersize - footersize; + // Payload can max be 255 bytes, so I think the following case is only valid for austrian meters + if(len < headersize) + len += 256; + + if((headersize + footersize + len) > ctx.length) + return DATA_PARSE_INCOMPLETE; + + MbusFooter* mf = (MbusFooter*) (d + len + headersize); + if(mf->flag != MBUS_END) + return DATA_PARSE_BOUNDRY_FLAG_MISSING; + if(checksum(d + headersize, len) != mf->fcs) + return DATA_PARSE_FOOTER_CHECKSUM_ERROR; + + ptr += 2; len -= 2; + + // Control information field + uint8_t ci = *ptr; + + // Skip CI, STSAP and DTSAP + ptr += 3; len -= 3; + + // Bits 7 6 5 4 3 2 1 0 + // 0 0 0 Finished Sequence number + uint8_t sequenceNumber = (ci & 0x0F); + if((ci & 0x10) == 0x00) { // Not finished yet + if(sequenceNumber == 0) { + if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ? + pos = 0; + } else if(buf == NULL || pos + len > 1024 || sequenceNumber != (lastSequenceNumber + 1)) { + return DATA_PARSE_FAIL; + } + memcpy(buf+pos, ptr, len); + pos += len; + lastSequenceNumber = sequenceNumber; + return DATA_PARSE_INTERMEDIATE_SEGMENT; + } else if(sequenceNumber > 0) { // This is the last frame of multiple, assembly needed + if(buf == NULL || pos + len > 1024 || sequenceNumber != (lastSequenceNumber + 1)) { + return DATA_PARSE_FAIL; + } + memcpy(buf+pos, ptr, len); + pos += len; + return DATA_PARSE_FINAL_SEGMENT; + } + return ptr-d; +} + +uint16_t MBUSParser::write(const uint8_t* d, DataParserContext &ctx) { + if(buf != NULL) { + memcpy((uint8_t *) d, buf, pos); + ctx.length = pos; + } + return 0; +} + +uint8_t MBUSParser::checksum(const uint8_t* p, int len) { + uint8_t ret = 0; + while(len--) + ret += *p++; + return ret; +} diff --git a/lib/lib_div/ams/MbusParser.h b/lib/lib_div/ams/MbusParser.h new file mode 100644 index 000000000..d0520aecc --- /dev/null +++ b/lib/lib_div/ams/MbusParser.h @@ -0,0 +1,35 @@ +#ifndef _MBUSPARSER_H +#define _MBUSPARSER_H + +#include "Arduino.h" +#include "DataParser.h" + +#define MBUS_START 0x68 +#define MBUS_END 0x16 +#define MBUS_FRAME_LENGTH_NOT_EQUAL -41 + +typedef struct MbusHeader { + uint8_t flag1; + uint8_t len1; + uint8_t len2; + uint8_t flag2; +} __attribute__((packed)) MbusHeader; + +typedef struct MbusFooter { + uint8_t fcs; + uint8_t flag; +} __attribute__((packed)) MbusFooter; + +class MBUSParser { +public: + int8_t parse(uint8_t *buf, DataParserContext &ctx); + ~MBUSParser(void); + uint16_t write(const uint8_t* d, DataParserContext &ctx); +private: + uint8_t lastSequenceNumber = 0; + uint16_t pos = 0; + uint8_t *buf = NULL; + uint8_t checksum(const uint8_t* p, int len); +}; + +#endif diff --git a/lib/lib_div/ams/Time.cpp b/lib/lib_div/ams/Time.cpp new file mode 100644 index 000000000..0dcb29f7e --- /dev/null +++ b/lib/lib_div/ams/Time.cpp @@ -0,0 +1,321 @@ +/* + time.c - low level time and date functions + Copyright (c) Michael Margolis 2009-2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + 1.0 6 Jan 2010 - initial release + 1.1 12 Feb 2010 - fixed leap year calculation error + 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) + 1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update + status, updated examples for Arduino 1.0, fixed ARM + compatibility issues, added TimeArduinoDue and TimeTeensy3 + examples, add error checking and messages to RTC examples, + add examples to DS1307RTC library. + 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 +*/ + +#if ARDUINO >= 100 +#include +#else +#include +#endif + +#include "TimeLib.h" + +static tmElements_t tm; // a cache of time elements +static time_t cacheTime; // the time the cache was updated +static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds + +void refreshCache(time_t t) { + if (t != cacheTime) { + breakTime(t, tm); + cacheTime = t; + } +} + +int hour() { // the hour now + return hour(now()); +} + +int hour(time_t t) { // the hour for the given time + refreshCache(t); + return tm.Hour; +} + +int hourFormat12() { // the hour now in 12 hour format + return hourFormat12(now()); +} + +int hourFormat12(time_t t) { // the hour for the given time in 12 hour format + refreshCache(t); + if( tm.Hour == 0 ) + return 12; // 12 midnight + else if( tm.Hour > 12) + return tm.Hour - 12 ; + else + return tm.Hour ; +} + +uint8_t isAM() { // returns true if time now is AM + return !isPM(now()); +} + +uint8_t isAM(time_t t) { // returns true if given time is AM + return !isPM(t); +} + +uint8_t isPM() { // returns true if PM + return isPM(now()); +} + +uint8_t isPM(time_t t) { // returns true if PM + return (hour(t) >= 12); +} + +int minute() { + return minute(now()); +} + +int minute(time_t t) { // the minute for the given time + refreshCache(t); + return tm.Minute; +} + +int second() { + return second(now()); +} + +int second(time_t t) { // the second for the given time + refreshCache(t); + return tm.Second; +} + +int day(){ + return(day(now())); +} + +int day(time_t t) { // the day for the given time (0-6) + refreshCache(t); + return tm.Day; +} + +int weekday() { // Sunday is day 1 + return weekday(now()); +} + +int weekday(time_t t) { + refreshCache(t); + return tm.Wday; +} + +int month(){ + return month(now()); +} + +int month(time_t t) { // the month for the given time + refreshCache(t); + return tm.Month; +} + +int year() { // as in Processing, the full four digit year: (2009, 2010 etc) + return year(now()); +} + +int year(time_t t) { // the year for the given time + refreshCache(t); + return tmYearToCalendar(tm.Year); +} + +/*============================================================================*/ +/* functions to convert to and from system time */ +/* These are for interfacing with time services and are not normally needed in a sketch */ + +// leap year calculator expects year argument as years offset from 1970 +#define LEAP_YEAR(Y) ( ((1970+(Y))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) ) + +static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 + +void breakTime(time_t timeInput, tmElements_t &tm){ +// break the given time_t into time components +// this is a more compact version of the C library localtime function +// note that year is offset from 1970 !!! + + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + + time = (uint32_t)timeInput; + tm.Second = time % 60; + time /= 60; // now it is minutes + tm.Minute = time % 60; + time /= 60; // now it is hours + tm.Hour = time % 24; + time /= 24; // now it is days + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.Year = year; // year is offset from 1970 + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days=0; + month=0; + monthLength=0; + for (month=0; month<12; month++) { + if (month==1) { // february + if (LEAP_YEAR(year)) { + monthLength=29; + } else { + monthLength=28; + } + } else { + monthLength = monthDays[month]; + } + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month +} + +time_t makeTime(const tmElements_t &tm){ +// assemble time elements into time_t +// note year argument is offset from 1970 (see macros in time.h to convert to other formats) +// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds= tm.Year*(SECS_PER_DAY * 365); + for (i = 0; i < tm.Year; i++) { + if (LEAP_YEAR(i)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.Month; i++) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 + } + } + seconds+= (tm.Day-1) * SECS_PER_DAY; + seconds+= tm.Hour * SECS_PER_HOUR; + seconds+= tm.Minute * SECS_PER_MIN; + seconds+= tm.Second; + return (time_t)seconds; +} +/*=====================================================*/ +/* Low level system time functions */ + +static uint32_t sysTime = 0; +static uint32_t prevMillis = 0; +static uint32_t nextSyncTime = 0; +static timeStatus_t Status = timeNotSet; + +getExternalTime getTimePtr; // pointer to external sync function +//setExternalTime setTimePtr; // not used in this version + +#ifdef TIME_DRIFT_INFO // define this to get drift data +time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync +#endif + + +time_t now() { + // calculate number of seconds passed since last call to now() + while (millis() - prevMillis >= 1000) { + // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference + sysTime++; + prevMillis += 1000; +#ifdef TIME_DRIFT_INFO + sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift +#endif + } + if (nextSyncTime <= sysTime) { + if (getTimePtr != 0) { + time_t t = getTimePtr(); + if (t != 0) { + setTime(t); + } else { + nextSyncTime = sysTime + syncInterval; + Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; + } + } + } + return (time_t)sysTime; +} + +void setTime(time_t t) { +#ifdef TIME_DRIFT_INFO + if(sysUnsyncedTime == 0) + sysUnsyncedTime = t; // store the time of the first call to set a valid Time +#endif + + sysTime = (uint32_t)t; + nextSyncTime = (uint32_t)t + syncInterval; + Status = timeSet; + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) +} + +void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ + // year can be given as full four digit year or two digts (2010 or 10 for 2010); + //it is converted to years since 1970 + if( yr > 99) + yr = yr - 1970; + else + yr += 30; + tm.Year = yr; + tm.Month = mnth; + tm.Day = dy; + tm.Hour = hr; + tm.Minute = min; + tm.Second = sec; + setTime(makeTime(tm)); +} + +void adjustTime(long adjustment) { + sysTime += adjustment; +} + +// indicates if time has been set and recently synchronized +timeStatus_t timeStatus() { + now(); // required to actually update the status + return Status; +} + +void setSyncProvider( getExternalTime getTimeFunction){ + getTimePtr = getTimeFunction; + nextSyncTime = sysTime; + now(); // this will sync the clock +} + +void setSyncInterval(time_t interval){ // set the number of seconds between re-sync + syncInterval = (uint32_t)interval; + nextSyncTime = sysTime + syncInterval; +} diff --git a/lib/lib_div/ams/TimeLib.h b/lib/lib_div/ams/TimeLib.h new file mode 100644 index 000000000..b587046e3 --- /dev/null +++ b/lib/lib_div/ams/TimeLib.h @@ -0,0 +1,144 @@ +/* + time.h - low level time and date functions +*/ + +/* + July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) + - fixed daysToTime_t macro (thanks maniacbug) +*/ + +#ifndef _Time_h +#ifdef __cplusplus +#define _Time_h + +#include +#ifndef __AVR__ +#include // for __time_t_defined, but avr libc lacks sys/types.h +#endif + + +#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc +typedef unsigned long time_t; +#endif + + +// This ugly hack allows us to define C++ overloaded functions, when included +// from within an extern "C", as newlib's sys/stat.h does. Actually it is +// intended to include "time.h" from the C library (on ARM, but AVR does not +// have that file at all). On Mac and Windows, the compiler will find this +// "Time.h" instead of the C library "time.h", so we may cause other weird +// and unpredictable effects by conflicting with the C library header "time.h", +// but at least this hack lets us define C++ functions as intended. Hopefully +// nothing too terrible will result from overriding the C library header?! +extern "C++" { +typedef enum {timeNotSet, timeNeedsSync, timeSet +} timeStatus_t ; + +typedef enum { + dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday +} timeDayOfWeek_t; + +typedef enum { + tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields +} tmByteFields; + +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + uint8_t Year; // offset from 1970; +} tmElements_t, TimeElements, *tmElementsPtr_t; + +//convenience macros to convert to and from tm years +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +#define CalendarYrToTm(Y) ((Y) - 1970) +#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 +#define y2kYearToTm(Y) ((Y) + 30) + +typedef time_t(*getExternalTime)(); +//typedef void (*setExternalTime)(const time_t); // not used in this version + + +/*==============================================================================*/ +/* Useful Constants */ +#define SECS_PER_MIN ((time_t)(60UL)) +#define SECS_PER_HOUR ((time_t)(3600UL)) +#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL)) +#define DAYS_PER_WEEK ((time_t)(7UL)) +#define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK)) +#define SECS_PER_YEAR ((time_t)(SECS_PER_DAY * 365UL)) // TODO: ought to handle leap years +#define SECS_YR_2000 ((time_t)(946684800UL)) // the time at the start of y2k + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) ((_time_) % SECS_PER_MIN) +#define numberOfMinutes(_time_) (((_time_) / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (((_time_) % SECS_PER_DAY) / SECS_PER_HOUR) +#define dayOfWeek(_time_) ((((_time_) / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday +#define elapsedDays(_time_) ((_time_) / SECS_PER_DAY) // this is number of days since Jan 1 1970 +#define elapsedSecsToday(_time_) ((_time_) % SECS_PER_DAY) // the number of seconds since last midnight +// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 +// Always set the correct time before setting alarms +#define previousMidnight(_time_) (((_time_) / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day +#define nextMidnight(_time_) (previousMidnight(_time_) + SECS_PER_DAY) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY)) // note that week starts on day 1 +#define previousSunday(_time_) ((_time_) - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time +#define nextSunday(_time_) (previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time + + +/* Useful Macros for converting elapsed time to a time_t */ +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) + +/*============================================================================*/ +/* time and date functions */ +int hour(); // the hour now +int hour(time_t t); // the hour for the given time +int hourFormat12(); // the hour now in 12 hour format +int hourFormat12(time_t t); // the hour for the given time in 12 hour format +uint8_t isAM(); // returns true if time now is AM +uint8_t isAM(time_t t); // returns true the given time is AM +uint8_t isPM(); // returns true if time now is PM +uint8_t isPM(time_t t); // returns true the given time is PM +int minute(); // the minute now +int minute(time_t t); // the minute for the given time +int second(); // the second now +int second(time_t t); // the second for the given time +int day(); // the day now +int day(time_t t); // the day for the given time +int weekday(); // the weekday now (Sunday is day 1) +int weekday(time_t t); // the weekday for the given time +int month(); // the month now (Jan is month 1) +int month(time_t t); // the month for the given time +int year(); // the full four digit year: (2009, 2010 etc) +int year(time_t t); // the year for the given time + +time_t now(); // return the current time as seconds since Jan 1 1970 +void setTime(time_t t); +void setTime(int hr,int min,int sec,int day, int month, int yr); +void adjustTime(long adjustment); + +/* date strings */ +#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) +char* monthStr(uint8_t month); +char* dayStr(uint8_t day); +char* monthShortStr(uint8_t month); +char* dayShortStr(uint8_t day); + +/* time sync functions */ +timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized +void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider +void setSyncInterval(time_t interval); // set the number of seconds between re-sync + +/* low level functions to convert to and from system time */ +void breakTime(time_t time, tmElements_t &tm); // break time_t into elements +time_t makeTime(const tmElements_t &tm); // convert time elements into time_t + +} // extern "C++" +#endif // __cplusplus +#endif /* _Time_h */ + diff --git a/lib/lib_div/ams/crc.cpp b/lib/lib_div/ams/crc.cpp new file mode 100644 index 000000000..33ab5a4ff --- /dev/null +++ b/lib/lib_div/ams/crc.cpp @@ -0,0 +1,29 @@ +#include "crc.h" + +uint16_t AMS_crc16_x25(const uint8_t* p, int len) +{ + uint16_t crc = UINT16_MAX; + + while(len--) + for (uint16_t i = 0, d = 0xff & *p++; i < 8; i++, d >>= 1) + crc = ((crc & 1) ^ (d & 1)) ? (crc >> 1) ^ 0x8408 : (crc >> 1); + + return (~crc << 8) | (~crc >> 8 & 0xff); +} + +uint16_t AMS_crc16 (const uint8_t *p, int len) { + uint16_t crc = 0; + + while (len--) { + int i; + crc ^= *p++; + for (i = 0 ; i < 8 ; ++i) { + if (crc & 1) + crc = (crc >> 1) ^ 0xa001; + else + crc = (crc >> 1); + } + } + + return crc; +} diff --git a/lib/lib_div/ams/crc.h b/lib/lib_div/ams/crc.h new file mode 100644 index 000000000..d90a75ad2 --- /dev/null +++ b/lib/lib_div/ams/crc.h @@ -0,0 +1,10 @@ +#ifndef _CRC_H +#define _CRC_H + +#include "Arduino.h" +#include + +uint16_t AMS_crc16(const uint8_t* p, int len); +uint16_t AMS_crc16_x25(const uint8_t* p, int len); + +#endif diff --git a/lib/lib_div/ams/han_Parser.cpp b/lib/lib_div/ams/han_Parser.cpp new file mode 100644 index 000000000..97bd2449c --- /dev/null +++ b/lib/lib_div/ams/han_Parser.cpp @@ -0,0 +1,273 @@ +#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) { + + 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}; + 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; + } +} diff --git a/lib/lib_div/ams/han_Parser.h b/lib/lib_div/ams/han_Parser.h new file mode 100644 index 000000000..1847865dd --- /dev/null +++ b/lib/lib_div/ams/han_Parser.h @@ -0,0 +1,47 @@ + +#ifndef _HAN_PARSER_H +#define _HAN_PARSER_H +#if defined __cplusplus +#include "Arduino.h" +#include "DataParsers.h" +#include "DataParser.h" +#include "Cosem.h" +#include "ntohll.h" + +#define BUF_SIZE_HAN (1280) + +int16_t serial_available(void); +uint8_t serial_read(void); + +class Han_Parser +{ +public: + Han_Parser(uint16_t (*)(uint8_t, uint8_t), uint8_t, uint8_t *, uint8_t *); + ~Han_Parser(void); + bool readHanPort(uint8_t **out, uint16_t *size); + int16_t unwrapData(uint8_t *buf, DataParserContext &context); + void printHanReadError(int16_t pos); + uint8_t encryptionKey[16]; + uint8_t authenticationKey[16]; + uint8_t hanBuffer[BUF_SIZE_HAN]; + int len = 0; +private: + uint16_t (*dispatch)(uint8_t, uint8_t); + int serial_available(void); + int serial_read(void); + int16_t serial_readBytes(uint8_t *, uint16_t); + HDLCParser *hdlcParser = NULL; + MBUSParser *mbusParser = NULL; + GBTParser *gbtParser = NULL; + GCMParser *gcmParser = NULL; + LLCParser *llcParser = NULL; + DLMSParser *dlmsParser = NULL; + DSMRParser *dsmrParser = NULL; + uint8_t encryption_key[16]; + uint8_t authentication_key[16]; + uint8_t meter; + bool serialInit = true; + bool Debug = true; +}; +#endif +#endif diff --git a/lib/lib_div/ams/hexutils.cpp b/lib/lib_div/ams/hexutils.cpp new file mode 100644 index 000000000..98d9ac717 --- /dev/null +++ b/lib/lib_div/ams/hexutils.cpp @@ -0,0 +1,23 @@ +#include "hexutils.h" + +String AMS_toHex(uint8_t* in) { + return AMS_toHex(in, sizeof(in)*2); +} + +String AMS_toHex(uint8_t* in, uint16_t size) { + String hex; + for(int i = 0; i < size; i++) { + if(in[i] < 0x10) { + hex += '0'; + } + hex += String(in[i], HEX); + } + hex.toUpperCase(); + return hex; +} + +void AMS_fromHex(uint8_t *out, String in, uint16_t size) { + for(int i = 0; i < size*2; i += 2) { + out[i/2] = strtol(in.substring(i, i+2).c_str(), 0, 16); + } +} diff --git a/lib/lib_div/ams/hexutils.h b/lib/lib_div/ams/hexutils.h new file mode 100644 index 000000000..47f2117af --- /dev/null +++ b/lib/lib_div/ams/hexutils.h @@ -0,0 +1,11 @@ +#ifndef _HEXUTILS_H +#define _HEXUTILS_H + +#include +#include "Arduino.h" + +String AMS_toHex(uint8_t* in); +String AMS_toHex(uint8_t* in, uint16_t size); +void AMS_fromHex(uint8_t *out, String in, uint16_t size); + +#endif diff --git a/lib/lib_div/ams/library.json b/lib/lib_div/ams/library.json new file mode 100644 index 000000000..5c19a8062 --- /dev/null +++ b/lib/lib_div/ams/library.json @@ -0,0 +1,17 @@ +{ + "name": "ams", + "version": "1.0", + "description": "ESP8266 ESP32 library for Advanced utility meters", + "license": "GPL", + "homepage": "https://github.com/arendst/Tasmota", + "frameworks": "*", + "platforms": "*", + "authors": + { + "name": "Gerhard Mutz", + "maintainer": true + }, + "build": { + "flags": [ "-I$PROJECT_DIR/include" ] + } +} diff --git a/lib/lib_div/ams/library.properties b/lib/lib_div/ams/library.properties new file mode 100755 index 000000000..472c784f0 --- /dev/null +++ b/lib/lib_div/ams/library.properties @@ -0,0 +1,17 @@ +name=AMS Parser + +version=1.2.0 + +author=Gunnar Skjold + +maintainer=https://github.com/gskjold + +sentence=Meter parsing Library for Espressif ESP32 and ESP8266 devices. + +paragraph=This library allows the ESP32 and ESP8266 devices to parse several meter protocolls + +category=Communication + +url=https://github.com/UtilitechAS/amsreader-firmware + +architectures=esp32,esp8266 diff --git a/lib/lib_div/ams/ntohll.cpp b/lib/lib_div/ams/ntohll.cpp new file mode 100644 index 000000000..25732434d --- /dev/null +++ b/lib/lib_div/ams/ntohll.cpp @@ -0,0 +1,5 @@ +#include "ntohll.h" + +uint64_t ntohll(uint64_t x) { + return (((uint64_t)ntohl((uint32_t)x)) << 32) + ntohl(x >> 32); +} \ No newline at end of file diff --git a/lib/lib_div/ams/ntohll.h b/lib/lib_div/ams/ntohll.h new file mode 100644 index 000000000..2bb1e5e09 --- /dev/null +++ b/lib/lib_div/ams/ntohll.h @@ -0,0 +1,8 @@ +#ifndef _NTOHLL_H +#define _NTOHLL_H + +#include "lwip/def.h" + +uint64_t ntohll(uint64_t x); + +#endif diff --git a/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino b/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino index 6de4e6719..f9faa90fb 100755 --- a/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino @@ -2932,7 +2932,7 @@ extern void W8960_SetGain(uint8_t sel, uint16_t value); } #ifdef USE_ENERGY_SENSOR if (!strncmp(lp, "enrg[", 5)) { - lp=GetNumericArgument(lp + 5, OPER_EQU, &fvar, gv); + lp = GetNumericArgument(lp + 5, OPER_EQU, &fvar, gv); while (*lp == ' ') lp++; switch ((uint32_t)fvar) { case 0: @@ -4478,7 +4478,9 @@ extern void W8960_SetGain(uint8_t sel, uint16_t value); } #endif //USE_ANGLE_FUNC + #if defined(USE_SML_M) && defined (USE_SML_SCRIPT_CMD) +uint32_t sml_status(uint32_t meter); extern char *SML_GetSVal(uint32_t index); if (!strncmp(lp, "sml[", 4)) { @@ -4547,13 +4549,7 @@ extern char *SML_GetSVal(uint32_t index); fvar = -99; } } else { - - -#if defined(ED300L) || defined(AS2020) - fvar = SML_Status(fvar1); -#else - fvar = 0; -#endif //ED300L + fvar = sml_status(fvar1); } goto nfuncexit; } diff --git a/tasmota/tasmota_xsns_sensor/xsns_53_sml.ino b/tasmota/tasmota_xsns_sensor/xsns_53_sml.ino index 3dbfe9e38..698fc02ca 100755 --- a/tasmota/tasmota_xsns_sensor/xsns_53_sml.ino +++ b/tasmota/tasmota_xsns_sensor/xsns_53_sml.ino @@ -35,6 +35,7 @@ #include + // use special no wait serial driver, should be always on #ifndef ESP32 #define SPECIAL_SS @@ -50,6 +51,9 @@ USE_ESP32_SW_SERIAL default off, uses a special combo driver that allows more then 3 serial ports on ESP32. define rec pins as negativ to use software serial + + USE_SML_AUTHKEY + rarely used , thus off by default */ // if you have to save more RAM you may disable these options by defines in user_config_override @@ -87,6 +91,11 @@ #define USE_SML_MEDIAN_FILTER #endif + +#ifdef USE_SML_DECRYPT +#include "han_Parser.h" +#endif + /* special options per meter 1: special binary SML option for meters that use a bit in the status register to sign import or export like ED300L, AS2020 or DTZ541 @@ -107,13 +116,22 @@ e.g. 1,=so2,2 set obis line mode on meter 1 3: serial buffers a. serial buffer size -b. serial irq buffer size +b. serial irq buffer size, a must be given +c. dumplog buffer size, default is 128 , a and b must be given e.g. 1,=so3,256,256 set serial buffers on meter 1 4: decrytion key, 16 bytes hex btw 32 chars without spaces or commas defining key switches decryption mode on +5: +authentication key, 16 bytes hex btw 32 chars without spaces or commas +needs USE_SML_AUTHKEY + +6: +synchronisation timout in milliseconds, after no serial data within this +time serial pointer is reset to zero + */ //#define MODBUS_DEBUG @@ -131,9 +149,9 @@ public: virtual ~SML_ESP32_SERIAL(); bool begin(uint32_t speed, uint32_t smode, int32_t recpin, int32_t trxpin); int32_t peek(void); - int32_t read(void) override; + int read(void) override; size_t write(uint8_t byte) override; - int32_t available(void) override; + int available(void) override; void flush(void) override; void setRxBufferSize(uint32_t size); void updateBaudRate(uint32_t baud); @@ -232,7 +250,7 @@ int32_t SML_ESP32_SERIAL::peek(void) { } } -int32_t SML_ESP32_SERIAL::read(void) { +int SML_ESP32_SERIAL::read(void) { if (hws) { return hws->read(); } else { @@ -243,7 +261,7 @@ int32_t SML_ESP32_SERIAL::read(void) { } } -int32_t SML_ESP32_SERIAL::available(void) { +int SML_ESP32_SERIAL::available(void) { if (hws) { return hws->available(); } else { @@ -350,6 +368,10 @@ typedef union { #define TMSBSIZ 256 #endif +#ifndef SML_STIMEOUT +#define SML_STIMEOUT 1000 +#endif + #define SO_DWS74_BUG 1 #define SO_OBIS_LINE 2 @@ -376,6 +398,8 @@ struct METER_DESC { uint8_t *sbuff; uint16_t spos; uint16_t sibsiz; + uint32_t lastms; + uint16_t tout_ms; uint8_t so_flags; char meter_id[METER_ID_SIZE]; #ifdef USE_SML_SPECOPT @@ -398,9 +422,13 @@ struct METER_DESC { TasmotaSerial *meter_ss; #endif // ESP8266 #ifdef USE_SML_DECRYPT - bool use_crypt; + bool use_crypt = false; uint8_t last_iob; uint8_t key[SML_CRYPT_SIZE]; + Han_Parser *hp; +#ifdef USE_SML_AUTHKEY + uint8_t auth[SML_CRYPT_SIZE]; +#endif #endif }; @@ -418,8 +446,6 @@ struct METER_DESC meter_desc[MAX_METERS]; #define VBUS_SYNC 0xaa #define SML_SYNC 0x77 -#define SML_CRYPT_SYNC1 0x7e -#define SML_CRYPT_SYNC2 0xa0 #define EBUS_SYNC 0xaa #define EBUS_ESC 0xa9 @@ -456,6 +482,7 @@ struct SML_GLOBS { uint8_t ser_act_meter_num = 0; uint16_t sml_logindex; char *log_data; + uint16_t logsize = SML_DUMP_SIZE; #if defined(ED300L) || defined(AS2020) || defined(DTZ541) || defined(USE_SML_SPECOPT) uint8_t sml_status[MAX_METERS]; uint8_t g_mindex; @@ -541,9 +568,9 @@ double sml_median(struct SML_MEDIAN_FILTER* mf, double in) { #define SML_SAVAILABLE Serial_available() #define SML_SREAD Serial_read() -#define SML_SPEAK Serial_peek() +#define SML_SPEEK Serial_peek() -bool Serial_available() { +uint16_t Serial_available() { uint8_t num = sml_globs.dump2log & 7; if (num < 1 || num > sml_globs.meters_used) num = 1; if (!meter_desc[num - 1].meter_ss) return 0; @@ -564,9 +591,15 @@ uint8_t Serial_peek() { return meter_desc[num - 1].meter_ss->peek(); } +void sml_dump_start(char c) { + sml_globs.log_data[0] = ':'; + sml_globs.log_data[1] = c; + sml_globs.sml_logindex = 2; +} + #define SML_EBUS_SKIP_SYNC_DUMPS -uint8_t *hdlc_decode(uint8_t *data, uint32_t dlen, uint8_t *key, uint16_t *size); +uint8_t *hdlc_decode(struct METER_DESC *mp, uint16_t *size); void dump2log(void) { int16_t index = 0, hcnt = 0; @@ -582,42 +615,23 @@ void dump2log(void) { struct METER_DESC *mp = &meter_desc[meter]; if (mp->use_crypt == true) { d_lastms = millis(); - sml_globs.log_data[0] = ':'; - sml_globs.log_data[1] = ' '; - sml_globs.sml_logindex = 2; - while ((millis() - d_lastms) < 40) { + while ((millis() - d_lastms) < 50) { while (SML_SAVAILABLE) { - uint8_t iob = SML_SREAD; - sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", iob); - if (sml_globs.sml_logindex < SML_DUMP_SIZE - 7) { - sml_globs.sml_logindex += 3; - } - // fill raw serial buffer - mp->sbuff[mp->spos] = iob; - mp->spos++; - if (mp->spos >= mp->sbsiz) { - mp->spos = mp->sbsiz - 1; - } - if (iob == SML_CRYPT_SYNC2 && mp->last_iob == SML_CRYPT_SYNC1) { - // frame start - mp->spos = 2; - mp->sbuff[0] = SML_CRYPT_SYNC1; - mp->sbuff[1] = SML_CRYPT_SYNC2; - } - mp->last_iob = iob; + d_lastms = millis(); uint16_t logsiz; - uint8_t *fbuff = hdlc_decode(mp->sbuff, mp->spos, meter_desc[meter].key, &logsiz); - if (fbuff) { - // we decoded a valid frame - AddLog(LOG_LEVEL_INFO, PSTR(">> decrypted block: %d bytes"), logsiz); + uint8_t *payload; + if (mp->hp->readHanPort(&payload, &logsiz)) { + if (logsiz > mp->sbsiz) { + logsiz = mp->sbsiz; + } + memmove(mp->sbuff, payload, logsiz); + AddLog(LOG_LEVEL_INFO, PSTR("decrypted block: %d bytes"), logsiz); uint16_t index = 0; while (logsiz) { - sml_globs.log_data[0] = ':'; - sml_globs.log_data[1] = '>'; - sml_globs.sml_logindex = 2; + sml_dump_start('>'); for (uint16_t cnt = 0; cnt < 16; cnt++) { - sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", fbuff[index++]); - if (sml_globs.sml_logindex < SML_DUMP_SIZE - 7) { + sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", mp->sbuff[index++]); + if (sml_globs.sml_logindex < sml_globs.logsize - 7) { sml_globs.sml_logindex += 3; } logsiz--; @@ -627,13 +641,24 @@ void dump2log(void) { } AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); } + } else { + // dump serial buffer + sml_dump_start(' '); + while (index < mp->spos) { + sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", mp->sbuff[index++]); + if (sml_globs.sml_logindex >= 32*3+2) { + AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); + sml_dump_start(' '); + } + } } } } - if (sml_globs.sml_logindex > 2) { - sml_globs.log_data[sml_globs.sml_logindex] = 0; - AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); - } + if (sml_globs.sml_logindex > 2) { + AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); + sml_dump_start(' '); + } + mp->hp->len = 0; return; } #endif @@ -655,12 +680,9 @@ void dump2log(void) { hcnt++; if (hcnt > 15) { // line complete, build asci chars - sml_globs.log_data[index] = '='; - index++; - sml_globs.log_data[index] = '>'; - index++; - sml_globs.log_data[index] = ' '; - index++; + sml_globs.log_data[index++] = '='; + sml_globs.log_data[index++] = '>'; + sml_globs.log_data[index++] = ' '; for (uint8_t ccnt = 0; ccnt < 16; ccnt++) { if (isprint(dchars[ccnt])) { sml_globs.log_data[index] = dchars[ccnt]; @@ -690,14 +712,12 @@ void dump2log(void) { if (sml_globs.sml_logindex > 2) { sml_globs.log_data[sml_globs.sml_logindex] = 0; AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); - sml_globs.log_data[0] = ':'; - sml_globs.log_data[1] = ' '; - sml_globs.sml_logindex = 2; + sml_dump_start(' '); } continue; } sml_globs.log_data[sml_globs.sml_logindex] = c; - if (sml_globs.sml_logindex < SML_DUMP_SIZE - 2) { + if (sml_globs.sml_logindex < sml_globs.logsize - 2) { sml_globs.sml_logindex++; } } @@ -710,12 +730,10 @@ void dump2log(void) { if (c == VBUS_SYNC) { sml_globs.log_data[sml_globs.sml_logindex] = 0; AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); - sml_globs.log_data[0] = ':'; - sml_globs.log_data[1] = ' '; - sml_globs.sml_logindex = 2; + sml_dump_start(' '); } sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c); - if (sml_globs.sml_logindex < SML_DUMP_SIZE - 7) { + if (sml_globs.sml_logindex < sml_globs.logsize - 7) { sml_globs.sml_logindex += 3; } } @@ -727,7 +745,7 @@ void dump2log(void) { while (SML_SAVAILABLE) { c = SML_SREAD; if (c == EBUS_SYNC) { - p = SML_SPEAK; + p = SML_SPEEK; if (p != EBUS_SYNC && sml_globs.sml_logindex > 5) { // new packet, plot last one sml_globs.log_data[sml_globs.sml_logindex] = 0; @@ -738,7 +756,7 @@ void dump2log(void) { continue; } sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c); - if (sml_globs.sml_logindex < SML_DUMP_SIZE - 7) { + if (sml_globs.sml_logindex < sml_globs.logsize - 7) { sml_globs.sml_logindex += 3; } } @@ -752,12 +770,10 @@ void dump2log(void) { if (c == SML_SYNC) { sml_globs.log_data[sml_globs.sml_logindex] = 0; AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); - sml_globs.log_data[0] = ':'; - sml_globs.log_data[1] = ' '; - sml_globs.sml_logindex = 2; + sml_dump_start(' '); } sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c); - if (sml_globs.sml_logindex < SML_DUMP_SIZE - 7) { + if (sml_globs.sml_logindex < sml_globs.logsize - 7) { sml_globs.sml_logindex += 3; } } @@ -766,25 +782,28 @@ void dump2log(void) { default: // raw dump d_lastms = millis(); - sml_globs.log_data[0] = ':'; - sml_globs.log_data[1] = ' '; - sml_globs.sml_logindex = 2; - while ((millis() - d_lastms) < 40) { - while (SML_SAVAILABLE) { + sml_dump_start(' '); + while ((millis() - d_lastms) < 40) { + while (SML_SAVAILABLE) { + d_lastms = millis(); + yield(); sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", SML_SREAD); - if (sml_globs.sml_logindex < SML_DUMP_SIZE - 7) { + if (sml_globs.sml_logindex < sml_globs.logsize - 7) { sml_globs.sml_logindex += 3; - } else { - break; + } + if (sml_globs.sml_logindex >= 32*3+2) { + AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); + sml_dump_start(' '); } - } + } } if (sml_globs.sml_logindex > 2) { sml_globs.log_data[sml_globs.sml_logindex] = 0; AddLogData(LOG_LEVEL_INFO, sml_globs.log_data); } break; - } + + } } } @@ -1158,27 +1177,24 @@ void sml_shift_in(uint32_t meters, uint32_t shard) { #ifdef USE_SML_DECRYPT if (mp->use_crypt) { - uint8_t iob = (uint8_t)mp->meter_ss->read(); - if (mp->spos < mp->sbsiz) { - mp->sbuff[mp->spos] = iob; - } - mp->spos++; - - if (iob == SML_CRYPT_SYNC2 && mp->last_iob == SML_CRYPT_SYNC1) { - // frame start - mp->spos = 2; - mp->sbuff[0] = SML_CRYPT_SYNC1; - mp->sbuff[1] = SML_CRYPT_SYNC2; - } - mp->last_iob = iob; - - uint16_t logsiz; - uint8_t *db = hdlc_decode(mp->sbuff, mp->spos, mp->key, &logsiz); - if (db) { - // we decoded a valid frame - memmove(mp->sbuff, db, logsiz); - AddLog(LOG_LEVEL_INFO, PSTR(">> decrypted block: %d bytes"), logsiz); - SML_Decode(meters); + if (mp->hp) { + uint32_t timediff = millis() - mp->lastms; + if (timediff > mp->tout_ms) { + mp->hp->len = 0; + mp->spos = 0; + AddLog(LOG_LEVEL_DEBUG, PSTR("SML: sync")); + } + mp->lastms = millis(); + uint16_t len; + uint8_t *payload; + if (mp->hp->readHanPort(&payload, &len)) { + if (len > mp->sbsiz) { + len = mp->sbsiz; + } + memmove(mp->sbuff, payload, len); + AddLog(LOG_LEVEL_INFO, PSTR(">> decrypted block: %d bytes"), len); + SML_Decode(meters); + } } return; } @@ -1220,11 +1236,19 @@ void sml_shift_in(uint32_t meters, uint32_t shard) { break; case 'R': // raw without shift + { + uint32_t timediff = millis() - mp->lastms; + if (timediff > mp->tout_ms) { + mp->spos = 0; + AddLog(LOG_LEVEL_DEBUG, PSTR("SML: sync")); + } + mp->lastms = millis(); mp->sbuff[mp->spos] = iob; mp->spos++; if (mp->spos > mp->sbsiz) { mp->spos = 0; } + } break; case 'k': // Kamstrup @@ -1338,6 +1362,8 @@ void sml_shift_in(uint32_t meters, uint32_t shard) { } +uint16_t sml_count = 0; + // polled every 50 ms void SML_Poll(void) { uint32_t meters; @@ -1352,6 +1378,14 @@ uint32_t meters; while (meter_desc[meters].meter_ss->available()) { sml_shift_in(meters, 0); } +/* + if (meter_desc[meters].meter_ss->available()) { + sml_count++; + uint8_t iob = meter_desc[meters].meter_ss->read(); + if (sml_count<5 || sml_count > 100) { + AddLog(LOG_LEVEL_INFO, PSTR(">> %02x - %d"),iob,sml_count); + } + }*/ } } } @@ -1400,6 +1434,54 @@ char *skip_double(char *cp) { return 0; } +uint8_t *sml_find(uint8_t *src, uint16_t ssize, uint8_t *pattern, uint16_t psize) { + //AddLog(LOG_LEVEL_INFO, PSTR(">> %02x %02x %02x %02x"),pattern[0],pattern[1],pattern[2],pattern[3]); + if (psize >= ssize) { + return 0; + } + for (uint32_t cnt = 0; cnt < ssize - psize; cnt++) { + if (!memcmp(src, pattern, psize)) { + return src; + } + src++; + } + return 0; +} + +double sml_get_obis_value(uint8_t *data) { + double out = 0; + CosemData *item = (CosemData *)data; + switch (item->base.type) { + case CosemTypeLongSigned: { + out = ntohs(item->ls.data); + break; + } + case CosemTypeLongUnsigned: { + out = ntohs(item->lu.data); + break; + } + case CosemTypeDLongSigned: { + out = ntohl(item->dlu.data); + break; + } + case CosemTypeDLongUnsigned: { + out = ntohl(item->dlu.data); + break; + } + case CosemTypeLong64Signed: { + out = ntohll(item->l64s.data); + break; + } + case CosemTypeLong64Unsigned: { + out = ntohll(item->l64u.data); + break; + } + } + return out; +} + + + void SML_Decode(uint8_t index) { const char *mp = (const char*)sml_globs.meter_p; @@ -1512,7 +1594,7 @@ void SML_Decode(uint8_t index) { SML_Immediate_MQTT((const char*)mp, vindex, mindex); sml_globs.dvalid[vindex] = 1; // get sfac - } else if (*mp=='d') { + } else if (*mp == 'd') { // calc deltas d ind 10 (eg every 10 secs) if (dindex < MAX_DVARS) { // only n indexes @@ -1583,12 +1665,73 @@ void SML_Decode(uint8_t index) { uint8_t val = hexnibble(*mp++) << 4; val |= hexnibble(*mp++); if (val != *cp++) { - found=0; + found = 0; } } else { // ebus modbus pzem vbus or raw - // XXHHHHSSUU - if (*mp == 'x') { + if (!strncmp(mp, "pm(", 3)) { + // pattern match + uint8_t dp = 0; + mp += 3; + // default to asci obis + uint8_t aflg = 3; + if (*mp == 'r') { + aflg = 0; + mp++; + } else if (*mp == 'h') { + aflg = 1; + mp++; + } + uint8_t pattern[64]; + // check for obis pattern + for (uint32_t cnt = 0; cnt < sizeof(pattern); cnt++) { + if (*mp == '@' || !*mp) { + break; + } + if (*mp == ')') { + mp++; + if ((aflg & 2) && (dp == 2)) { + pattern[cnt] = 0xff; + cnt++; + } + pattern[cnt] = 0; + uint8_t *ucp = sml_find(cp, meter_desc[index].sbsiz, pattern, cnt); + if (ucp) { + cp = ucp + cnt; + // check auto type + if (aflg & 1) { + // METER_ID_SIZE + CosemData *item = (CosemData *)cp; + switch (item->base.type) { + case CosemTypeString: + memcpy(meter_desc[mindex].meter_id, item->str.data, item->str.length); + meter_desc[mindex].meter_id[item->str.length] = 0; + break; + case CosemTypeOctetString: + memcpy(meter_desc[mindex].meter_id, item->oct.data, item->oct.length); + meter_desc[mindex].meter_id[item->oct.length] = 0; + break; + default: + ebus_dval = sml_get_obis_value(cp); + } + } + } + break; + } + uint8_t iob; + if (aflg & 2) { + iob = strtol((char*)mp, (char**)&mp, 10); + if (*mp == '.') { + mp++; + dp++; + } + } else { + iob = hexnibble(*mp++) << 4; + iob |= hexnibble(*mp++); + } + pattern[cnt] = iob; + } + } else if (*mp == 'x') { if (*(mp + 1) == 'x') { //ignore one byte mp += 2; @@ -1873,7 +2016,6 @@ void SML_Decode(uint8_t index) { meter_desc[mindex].meter_id[p] = *cp++; } meter_desc[mindex].meter_id[p] = 0; - } else if (sml_globs.mp[mindex].type == 'k') { // 220901 uint32_t date = mbus_dval; @@ -1889,7 +2031,7 @@ void SML_Decode(uint8_t index) { } else { double dval; char type = sml_globs.mp[mindex].type; - if (type != 'e' && type != 'r' && type != 'm' && type != 'M' && type != 'k' && type != 'p' && type != 'v') { + if (type != 'e' && type != 'r' && type != 'R' && type != 'm' && type != 'M' && type != 'k' && type != 'p' && type != 'v') { // get numeric values if (type == 'o' || type == 'c') { if (*mp == '(') { @@ -2220,7 +2362,6 @@ void SML_Show(boolean json) { } - #ifdef USE_DOMOTICZ if (json && !TasmotaGlobal.tele_period) { char str[16]; @@ -2274,7 +2415,6 @@ void IRAM_ATTR SML_CounterIsr(void *arg) { sml_counter_pinstate ^= (1 << index); } - #ifndef METER_DEF_SIZE #define METER_DEF_SIZE 3000 #endif @@ -2336,161 +2476,169 @@ bool Gpio_used(uint8_t gpiopin) { #define SML_MINSB 64 char *SpecOptions(char *cp, uint32_t mnum) { // special option +struct METER_DESC *mp = &meter_desc[mnum]; switch (*cp) { case '1': cp++; #ifdef USE_SML_SPECOPT if (*cp == ',') { cp++; - meter_desc[mnum].so_obis1 = strtol(cp, &cp, 16); + mp->so_obis1 = strtol(cp, &cp, 16); } if (*cp == ',') { cp++; - meter_desc[mnum].so_fcode1 = strtol(cp, &cp, 16); + mp->so_fcode1 = strtol(cp, &cp, 16); } if (*cp == ',') { cp++; - meter_desc[mnum].so_bpos1 = strtol(cp, &cp, 10); + mp->so_bpos1 = strtol(cp, &cp, 10); } if (*cp == ',') { cp++; - meter_desc[mnum].so_fcode2 = strtol(cp, &cp, 16); + mp->so_fcode2 = strtol(cp, &cp, 16); } if (*cp == ',') { cp++; - meter_desc[mnum].so_bpos2 = strtol(cp, &cp, 10); + mp->so_bpos2 = strtol(cp, &cp, 10); } if (*cp == ',') { cp++; - meter_desc[mnum].so_obis2 = strtol(cp, &cp, 16); + mp->so_obis2 = strtol(cp, &cp, 16); } #endif break; case '2': cp += 2; - meter_desc[mnum].so_flags = strtol(cp, &cp, 16); + mp->so_flags = strtol(cp, &cp, 16); break; case '3': cp += 2; - meter_desc[mnum].sbsiz = strtol(cp, &cp, 10); + mp->sbsiz = strtol(cp, &cp, 10); if (*cp == ',') { cp++; - meter_desc[mnum].sibsiz = strtol(cp, &cp, 10); - if (meter_desc[mnum].sibsiz < SML_MINSB) { - meter_desc[mnum].sibsiz = SML_MINSB; + mp->sibsiz = strtol(cp, &cp, 10); + if (mp->sibsiz < SML_MINSB) { + mp->sibsiz = SML_MINSB; } } + if (*cp == ',') { + cp++; + sml_globs.logsize = strtol(cp, &cp, 10); + } break; case '4': cp += 2; #ifdef USE_SML_DECRYPT meter_desc[mnum].use_crypt = true; for (uint8_t cnt = 0; cnt < (SML_CRYPT_SIZE * 2); cnt += 2) { - meter_desc[mnum].key[cnt / 2] = (sml_hexnibble(cp[cnt]) << 4) | sml_hexnibble(cp[cnt + 1]); + mp->key[cnt / 2] = (sml_hexnibble(cp[cnt]) << 4) | sml_hexnibble(cp[cnt + 1]); } -#endif + AddLog(LOG_LEVEL_INFO, PSTR("SML: crypto mode used for meter %d"), mnum + 1); break; +#ifdef USE_SML_AUTHKEY + case '5': + cp += 2; + for (uint8_t cnt = 0; cnt < (SML_CRYPT_SIZE * 2); cnt += 2) { + mp->auth[cnt / 2] = (sml_hexnibble(cp[cnt]) << 4) | sml_hexnibble(cp[cnt + 1]); + } + break; + case '6': + cp += 2; + mp->tout_ms = strtol(cp, &cp, 10); + break; +#endif +#endif } return cp; } #ifdef USE_SML_DECRYPT - -//// calculate crc16 CCITT -uint16_t hdlc_crc16(const uint8_t *dp, uint8_t len) { -#define POLY 0x8408 - uint8_t i; - uint16_t data; - uint16_t crc = 0xffff; - - if (len == 0) { - return (~crc); +uint16_t serial_dispatch(uint8_t meter, uint8_t sel) { + struct METER_DESC *mp = &meter_desc[meter]; + if (!sel) { + return mp->meter_ss->available(); } - do { - data = (unsigned int)0xff & *dp++; - for (i = 0; i < 8; i++) { - if ((crc & 0x0001) ^ (data & 0x0001)) { - crc = (crc >> 1) ^ POLY; - } else { - crc >>= 1; - } - data >>= 1; - } - } while (--len); - - crc = ~crc; - data = crc; - crc = (crc << 8) | (data >> 8 & 0xff); - return crc; + uint8_t iob = mp->meter_ss->read(); + return iob; } -uint8_t *hdlc_decode(uint8_t *data, uint32_t dlen, uint8_t *key, uint16_t *size) { - if (dlen < 31) { - return 0; +int SML_print(const char *format, ...) { + static char loc_buf[64]; + char* temp = loc_buf; + int len; + va_list arg; + va_list copy; + va_start(arg, format); + va_copy(copy, arg); + len = vsnprintf(NULL, 0, format, arg); + va_end(copy); + if (len >= sizeof(loc_buf)) { + temp = (char*)malloc(len + 1); + if (temp == NULL) { + return 0; + } } - uint16_t crc = hdlc_crc16(data + 1, dlen - 4); - uint16_t dcrc = data[dlen - 3] << 8 | data[dlen - 2]; - if (crc != dcrc) { - return 0; + vsnprintf(temp, len + 1, format, arg); + AddLog(LOG_LEVEL_DEBUG, PSTR("SML: %s"),temp); + va_end(arg); + if (len >= sizeof(loc_buf)) { + free(temp); } - - // crc OK - uint8_t ivec[12]; - uint8_t index = 0; - for (uint8_t cnt = 14; cnt < 14+8; cnt++) { - ivec[index] = data[cnt]; - index++; - } - for (uint8_t cnt = 24; cnt < 24+4; cnt++) { - ivec[index] = data[cnt]; - index++; - } - - br_gcm_context gcm_ctx; - br_aes_small_ctr_keys ctr_ctx; - br_aes_small_ctr_init(&ctr_ctx, key, 16); - br_gcm_init(&gcm_ctx, &ctr_ctx.vtable, &br_ghash_ctmul32); - br_gcm_reset(&gcm_ctx, ivec, 12); - br_gcm_flip(&gcm_ctx); - br_gcm_run(&gcm_ctx, 0, data + 28 , dlen - 31); - *size = dlen - 31; - return data + 28; + return len; } -#endif +#endif // USE_SML_DECRYPT void reset_sml_vars(uint16_t maxmeters) { for (uint32_t meters = 0; meters < maxmeters; meters++) { - meter_desc[meters].spos = 0; - meter_desc[meters].sbsiz = SML_BSIZ; - meter_desc[meters].sibsiz = TMSBSIZ; - if (meter_desc[meters].sbuff) { - free(meter_desc[meters].sbuff); - meter_desc[meters].sbuff = 0; + + struct METER_DESC *mp = &meter_desc[meters]; + mp->spos = 0; + mp->sbsiz = SML_BSIZ; + mp->sibsiz = TMSBSIZ; + if (mp->sbuff) { + free(mp->sbuff); + mp->sbuff = 0; } #ifdef USE_SML_SPECOPT - meter_desc[meters].so_obis1 = 0; - meter_desc[meters].so_obis2 = 0; + mp->so_obis1 = 0; + mp->so_obis2 = 0; #endif - meter_desc[meters].so_flags = 0; + mp->so_flags = 0; // addresses a bug in meter DWS74 #ifdef DWS74_BUG - meter_desc[meters].so_flags |= SO_DWS74_BUG; + mp->so_flags |= SO_DWS74_BUG; #endif #ifdef SML_OBIS_LINE - meter_desc[meters].so_flags |= SO_OBIS_LINE; + mp->so_flags |= SO_OBIS_LINE; #endif - if (meter_desc[meters].txmem) { - free(meter_desc[meters].txmem); - meter_desc[meters].txmem = 0; + if (mp->txmem) { + free(mp->txmem); + mp->txmem = 0; } - meter_desc[meters].txmem = 0; - meter_desc[meters].trxpin = -1; - if (meter_desc[meters].meter_ss) { - delete meter_desc[meters].meter_ss; - meter_desc[meters].meter_ss = NULL; + mp->txmem = 0; + mp->trxpin = -1; + if (mp->meter_ss) { + delete mp->meter_ss; + mp->meter_ss = NULL; } + + mp->lastms = millis(); + mp->tout_ms = SML_STIMEOUT; + +#ifdef USE_SML_DECRYPT + if (mp->use_crypt) { + if (mp->hp) { + delete mp->hp; + mp->hp = NULL; + } + } + mp->use_crypt = 0; +#ifdef USE_SML_AUTHKEY + memset(mp->auth, 0, SML_CRYPT_SIZE); +#endif +#endif // USE_SML_DECRYPT } } @@ -2940,6 +3088,16 @@ next_line: } else { mp->shift_mode = (type != 'o' && type != 'e' && type != 'k' && type != 'm' && type != 'M' && type != 'p' && type != 'R' && type != 'v'); } + +#ifdef USE_SML_DECRYPT + if (mp->use_crypt) { +#ifdef USE_SML_AUTHKEY + mp->hp = new Han_Parser(serial_dispatch, meters, mp->key, mp->auth); +#else + mp->hp = new Han_Parser(serial_dispatch, meters, mp->key, nullptr); +#endif + } +#endif } sml_globs.ready = true; @@ -3462,22 +3620,24 @@ bool XSNS_53_cmd(void) { char *cp = XdrvMailbox.data; if (*cp == 'd') { // set dump mode - cp++; - uint8_t index = atoi(cp); - if ((index & 7) > sml_globs.meters_used) index = 1; - if (index > 0 && sml_globs.mp[(index & 7) - 1].type == 'c') { - index = 0; - } - if (sml_globs.log_data) { - free(sml_globs.log_data); - sml_globs.log_data = 0; - } + if (sml_globs.ready) { + cp++; + uint8_t index = atoi(cp); + if ((index & 7) > sml_globs.meters_used) index = 1; + if (index > 0 && sml_globs.mp[(index & 7) - 1].type == 'c') { + index = 0; + } + if (sml_globs.log_data) { + free(sml_globs.log_data); + sml_globs.log_data = 0; + } - if (index > 0) { - sml_globs.log_data = (char*)calloc(SML_DUMP_SIZE, sizeof(char)); + if (index > 0) { + sml_globs.log_data = (char*)calloc(sml_globs.logsize, sizeof(char)); + } + sml_globs.dump2log = index; + ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"dump: %d\"}}"), sml_globs.dump2log); } - sml_globs.dump2log = index; - ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"dump: %d\"}}"), sml_globs.dump2log); } else if (*cp == 'c') { // set counter cp++; @@ -3498,38 +3658,38 @@ bool XSNS_53_cmd(void) { } } ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"counter%d: %d\"}}"), index, RtcSettings.pulse_counter[index - 1]); - } else if (*cp=='r') { + } else if (*cp == 'r') { // restart ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"restart\"}}")); SML_CounterSaveState(); SML_Init(); - } else if (*cp=='m') { + } else if (*cp == 'm') { // meter number for serial activity cp++; if (!isdigit(*cp)) { - ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_meter_num: %d\"}}"),sml_globs.ser_act_meter_num); + ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_meter_num: %d\"}}"), sml_globs.ser_act_meter_num); } else { - sml_globs.ser_act_meter_num=atoi(cp); - ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_meter_num: %d\"}}"),sml_globs.ser_act_meter_num); + sml_globs.ser_act_meter_num = atoi(cp); + ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_meter_num: %d\"}}"), sml_globs.ser_act_meter_num); } - } else if (*cp=='l') { + } else if (*cp == 'l') { // serial activity LED-GPIO cp++; if (!isdigit(*cp)) { - ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_LED_pin: %d\"}}"),sml_globs.ser_act_LED_pin); + ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_LED_pin: %d\"}}"), sml_globs.ser_act_LED_pin); } else { - sml_globs.ser_act_LED_pin=atoi(cp); + sml_globs.ser_act_LED_pin = atoi(cp); if (Gpio_used(sml_globs.ser_act_LED_pin)) { - AddLog(LOG_LEVEL_INFO, PSTR("SML: Error: Duplicate GPIO %d defined. Not usable for LED."),sml_globs.ser_act_LED_pin); - sml_globs.ser_act_LED_pin=255; + AddLog(LOG_LEVEL_INFO, PSTR("SML: Error: Duplicate GPIO %d defined. Not usable for LED."), sml_globs.ser_act_LED_pin); + sml_globs.ser_act_LED_pin = 255; } - if (sml_globs.ser_act_LED_pin!=255) { + if (sml_globs.ser_act_LED_pin != 255) { pinMode(sml_globs.ser_act_LED_pin, OUTPUT); } - ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_LED_pin: %d\"}}"),sml_globs.ser_act_LED_pin); + ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_LED_pin: %d\"}}"), sml_globs.ser_act_LED_pin); } } else { - serviced=false; + serviced = false; } } return serviced;