mirror of https://github.com/arendst/Tasmota.git
Sml ams (#17828)
* sml ams crypto support * add ams library * fix crc names * fix TLS dependency * Update library.properties * Update xsns_53_sml.ino
This commit is contained in:
parent
9e9afe88f1
commit
f09a083777
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,104 @@
|
|||
|
||||
#include "GcmParser.h"
|
||||
#include "tasmota_options.h"
|
||||
|
||||
#ifdef USE_TLS
|
||||
#include "lwip/def.h"
|
||||
#include <t_bearssl.h>
|
||||
|
||||
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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
#include "LlcParser.h"
|
||||
|
||||
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
|
||||
ctx.length -= 3;
|
||||
return 3;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 <Arduino.h>
|
||||
#else
|
||||
#include <WProgram.h>
|
||||
#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;
|
||||
}
|
|
@ -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 <inttypes.h>
|
||||
#ifndef __AVR__
|
||||
#include <sys/types.h> // 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 */
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
#ifndef _CRC_H
|
||||
#define _CRC_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <stdint.h>
|
||||
|
||||
uint16_t AMS_crc16(const uint8_t* p, int len);
|
||||
uint16_t AMS_crc16_x25(const uint8_t* p, int len);
|
||||
|
||||
#endif
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef _HEXUTILS_H
|
||||
#define _HEXUTILS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
|
@ -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" ]
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
#include "ntohll.h"
|
||||
|
||||
uint64_t ntohll(uint64_t x) {
|
||||
return (((uint64_t)ntohl((uint32_t)x)) << 32) + ntohl(x >> 32);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef _NTOHLL_H
|
||||
#define _NTOHLL_H
|
||||
|
||||
#include "lwip/def.h"
|
||||
|
||||
uint64_t ntohll(uint64_t x);
|
||||
|
||||
#endif
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include <TasmotaSerial.h>
|
||||
|
||||
|
||||
// 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);
|
||||
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,16 +782,18 @@ void dump2log(void) {
|
|||
default:
|
||||
// raw dump
|
||||
d_lastms = millis();
|
||||
sml_globs.log_data[0] = ':';
|
||||
sml_globs.log_data[1] = ' ';
|
||||
sml_globs.sml_logindex = 2;
|
||||
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(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -784,6 +802,7 @@ void dump2log(void) {
|
|||
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1158,28 +1177,25 @@ 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;
|
||||
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->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->lastms = millis();
|
||||
uint16_t len;
|
||||
uint8_t *payload;
|
||||
if (mp->hp->readHanPort(&payload, &len)) {
|
||||
if (len > mp->sbsiz) {
|
||||
len = mp->sbsiz;
|
||||
}
|
||||
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);
|
||||
memmove(mp->sbuff, payload, len);
|
||||
AddLog(LOG_LEVEL_INFO, PSTR(">> decrypted block: %d bytes"), len);
|
||||
SML_Decode(meters);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
@ -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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// crc OK
|
||||
uint8_t ivec[12];
|
||||
uint8_t index = 0;
|
||||
for (uint8_t cnt = 14; cnt < 14+8; cnt++) {
|
||||
ivec[index] = data[cnt];
|
||||
index++;
|
||||
vsnprintf(temp, len + 1, format, arg);
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR("SML: %s"),temp);
|
||||
va_end(arg);
|
||||
if (len >= sizeof(loc_buf)) {
|
||||
free(temp);
|
||||
}
|
||||
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,6 +3620,7 @@ bool XSNS_53_cmd(void) {
|
|||
char *cp = XdrvMailbox.data;
|
||||
if (*cp == 'd') {
|
||||
// set dump mode
|
||||
if (sml_globs.ready) {
|
||||
cp++;
|
||||
uint8_t index = atoi(cp);
|
||||
if ((index & 7) > sml_globs.meters_used) index = 1;
|
||||
|
@ -3474,10 +3633,11 @@ bool XSNS_53_cmd(void) {
|
|||
}
|
||||
|
||||
if (index > 0) {
|
||||
sml_globs.log_data = (char*)calloc(SML_DUMP_SIZE, sizeof(char));
|
||||
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);
|
||||
}
|
||||
} 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;
|
||||
|
|
Loading…
Reference in New Issue