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
|
#ifdef USE_ENERGY_SENSOR
|
||||||
if (!strncmp(lp, "enrg[", 5)) {
|
if (!strncmp(lp, "enrg[", 5)) {
|
||||||
lp=GetNumericArgument(lp + 5, OPER_EQU, &fvar, gv);
|
lp = GetNumericArgument(lp + 5, OPER_EQU, &fvar, gv);
|
||||||
while (*lp == ' ') lp++;
|
while (*lp == ' ') lp++;
|
||||||
switch ((uint32_t)fvar) {
|
switch ((uint32_t)fvar) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -4478,7 +4478,9 @@ extern void W8960_SetGain(uint8_t sel, uint16_t value);
|
||||||
}
|
}
|
||||||
#endif //USE_ANGLE_FUNC
|
#endif //USE_ANGLE_FUNC
|
||||||
|
|
||||||
|
|
||||||
#if defined(USE_SML_M) && defined (USE_SML_SCRIPT_CMD)
|
#if defined(USE_SML_M) && defined (USE_SML_SCRIPT_CMD)
|
||||||
|
uint32_t sml_status(uint32_t meter);
|
||||||
extern char *SML_GetSVal(uint32_t index);
|
extern char *SML_GetSVal(uint32_t index);
|
||||||
|
|
||||||
if (!strncmp(lp, "sml[", 4)) {
|
if (!strncmp(lp, "sml[", 4)) {
|
||||||
|
@ -4547,13 +4549,7 @@ extern char *SML_GetSVal(uint32_t index);
|
||||||
fvar = -99;
|
fvar = -99;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
fvar = sml_status(fvar1);
|
||||||
|
|
||||||
#if defined(ED300L) || defined(AS2020)
|
|
||||||
fvar = SML_Status(fvar1);
|
|
||||||
#else
|
|
||||||
fvar = 0;
|
|
||||||
#endif //ED300L
|
|
||||||
}
|
}
|
||||||
goto nfuncexit;
|
goto nfuncexit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include <TasmotaSerial.h>
|
#include <TasmotaSerial.h>
|
||||||
|
|
||||||
|
|
||||||
// use special no wait serial driver, should be always on
|
// use special no wait serial driver, should be always on
|
||||||
#ifndef ESP32
|
#ifndef ESP32
|
||||||
#define SPECIAL_SS
|
#define SPECIAL_SS
|
||||||
|
@ -50,6 +51,9 @@
|
||||||
USE_ESP32_SW_SERIAL
|
USE_ESP32_SW_SERIAL
|
||||||
default off, uses a special combo driver that allows more then 3 serial ports on ESP32.
|
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
|
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
|
// 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
|
#define USE_SML_MEDIAN_FILTER
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef USE_SML_DECRYPT
|
||||||
|
#include "han_Parser.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/* special options per meter
|
/* special options per meter
|
||||||
1:
|
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
|
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:
|
3:
|
||||||
serial buffers
|
serial buffers
|
||||||
a. serial buffer size
|
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
|
e.g. 1,=so3,256,256 set serial buffers on meter 1
|
||||||
|
|
||||||
4:
|
4:
|
||||||
decrytion key, 16 bytes hex btw 32 chars without spaces or commas
|
decrytion key, 16 bytes hex btw 32 chars without spaces or commas
|
||||||
defining key switches decryption mode on
|
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
|
//#define MODBUS_DEBUG
|
||||||
|
@ -131,9 +149,9 @@ public:
|
||||||
virtual ~SML_ESP32_SERIAL();
|
virtual ~SML_ESP32_SERIAL();
|
||||||
bool begin(uint32_t speed, uint32_t smode, int32_t recpin, int32_t trxpin);
|
bool begin(uint32_t speed, uint32_t smode, int32_t recpin, int32_t trxpin);
|
||||||
int32_t peek(void);
|
int32_t peek(void);
|
||||||
int32_t read(void) override;
|
int read(void) override;
|
||||||
size_t write(uint8_t byte) override;
|
size_t write(uint8_t byte) override;
|
||||||
int32_t available(void) override;
|
int available(void) override;
|
||||||
void flush(void) override;
|
void flush(void) override;
|
||||||
void setRxBufferSize(uint32_t size);
|
void setRxBufferSize(uint32_t size);
|
||||||
void updateBaudRate(uint32_t baud);
|
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) {
|
if (hws) {
|
||||||
return hws->read();
|
return hws->read();
|
||||||
} else {
|
} 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) {
|
if (hws) {
|
||||||
return hws->available();
|
return hws->available();
|
||||||
} else {
|
} else {
|
||||||
|
@ -350,6 +368,10 @@ typedef union {
|
||||||
#define TMSBSIZ 256
|
#define TMSBSIZ 256
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef SML_STIMEOUT
|
||||||
|
#define SML_STIMEOUT 1000
|
||||||
|
#endif
|
||||||
|
|
||||||
#define SO_DWS74_BUG 1
|
#define SO_DWS74_BUG 1
|
||||||
#define SO_OBIS_LINE 2
|
#define SO_OBIS_LINE 2
|
||||||
|
|
||||||
|
@ -376,6 +398,8 @@ struct METER_DESC {
|
||||||
uint8_t *sbuff;
|
uint8_t *sbuff;
|
||||||
uint16_t spos;
|
uint16_t spos;
|
||||||
uint16_t sibsiz;
|
uint16_t sibsiz;
|
||||||
|
uint32_t lastms;
|
||||||
|
uint16_t tout_ms;
|
||||||
uint8_t so_flags;
|
uint8_t so_flags;
|
||||||
char meter_id[METER_ID_SIZE];
|
char meter_id[METER_ID_SIZE];
|
||||||
#ifdef USE_SML_SPECOPT
|
#ifdef USE_SML_SPECOPT
|
||||||
|
@ -398,9 +422,13 @@ struct METER_DESC {
|
||||||
TasmotaSerial *meter_ss;
|
TasmotaSerial *meter_ss;
|
||||||
#endif // ESP8266
|
#endif // ESP8266
|
||||||
#ifdef USE_SML_DECRYPT
|
#ifdef USE_SML_DECRYPT
|
||||||
bool use_crypt;
|
bool use_crypt = false;
|
||||||
uint8_t last_iob;
|
uint8_t last_iob;
|
||||||
uint8_t key[SML_CRYPT_SIZE];
|
uint8_t key[SML_CRYPT_SIZE];
|
||||||
|
Han_Parser *hp;
|
||||||
|
#ifdef USE_SML_AUTHKEY
|
||||||
|
uint8_t auth[SML_CRYPT_SIZE];
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -418,8 +446,6 @@ struct METER_DESC meter_desc[MAX_METERS];
|
||||||
|
|
||||||
#define VBUS_SYNC 0xaa
|
#define VBUS_SYNC 0xaa
|
||||||
#define SML_SYNC 0x77
|
#define SML_SYNC 0x77
|
||||||
#define SML_CRYPT_SYNC1 0x7e
|
|
||||||
#define SML_CRYPT_SYNC2 0xa0
|
|
||||||
#define EBUS_SYNC 0xaa
|
#define EBUS_SYNC 0xaa
|
||||||
#define EBUS_ESC 0xa9
|
#define EBUS_ESC 0xa9
|
||||||
|
|
||||||
|
@ -456,6 +482,7 @@ struct SML_GLOBS {
|
||||||
uint8_t ser_act_meter_num = 0;
|
uint8_t ser_act_meter_num = 0;
|
||||||
uint16_t sml_logindex;
|
uint16_t sml_logindex;
|
||||||
char *log_data;
|
char *log_data;
|
||||||
|
uint16_t logsize = SML_DUMP_SIZE;
|
||||||
#if defined(ED300L) || defined(AS2020) || defined(DTZ541) || defined(USE_SML_SPECOPT)
|
#if defined(ED300L) || defined(AS2020) || defined(DTZ541) || defined(USE_SML_SPECOPT)
|
||||||
uint8_t sml_status[MAX_METERS];
|
uint8_t sml_status[MAX_METERS];
|
||||||
uint8_t g_mindex;
|
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_SAVAILABLE Serial_available()
|
||||||
#define SML_SREAD Serial_read()
|
#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;
|
uint8_t num = sml_globs.dump2log & 7;
|
||||||
if (num < 1 || num > sml_globs.meters_used) num = 1;
|
if (num < 1 || num > sml_globs.meters_used) num = 1;
|
||||||
if (!meter_desc[num - 1].meter_ss) return 0;
|
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();
|
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
|
#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) {
|
void dump2log(void) {
|
||||||
int16_t index = 0, hcnt = 0;
|
int16_t index = 0, hcnt = 0;
|
||||||
|
@ -582,42 +615,23 @@ void dump2log(void) {
|
||||||
struct METER_DESC *mp = &meter_desc[meter];
|
struct METER_DESC *mp = &meter_desc[meter];
|
||||||
if (mp->use_crypt == true) {
|
if (mp->use_crypt == true) {
|
||||||
d_lastms = millis();
|
d_lastms = millis();
|
||||||
sml_globs.log_data[0] = ':';
|
while ((millis() - d_lastms) < 50) {
|
||||||
sml_globs.log_data[1] = ' ';
|
|
||||||
sml_globs.sml_logindex = 2;
|
|
||||||
while ((millis() - d_lastms) < 40) {
|
|
||||||
while (SML_SAVAILABLE) {
|
while (SML_SAVAILABLE) {
|
||||||
uint8_t iob = SML_SREAD;
|
d_lastms = millis();
|
||||||
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;
|
|
||||||
uint16_t logsiz;
|
uint16_t logsiz;
|
||||||
uint8_t *fbuff = hdlc_decode(mp->sbuff, mp->spos, meter_desc[meter].key, &logsiz);
|
uint8_t *payload;
|
||||||
if (fbuff) {
|
if (mp->hp->readHanPort(&payload, &logsiz)) {
|
||||||
// we decoded a valid frame
|
if (logsiz > mp->sbsiz) {
|
||||||
AddLog(LOG_LEVEL_INFO, PSTR(">> decrypted block: %d bytes"), logsiz);
|
logsiz = mp->sbsiz;
|
||||||
|
}
|
||||||
|
memmove(mp->sbuff, payload, logsiz);
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("decrypted block: %d bytes"), logsiz);
|
||||||
uint16_t index = 0;
|
uint16_t index = 0;
|
||||||
while (logsiz) {
|
while (logsiz) {
|
||||||
sml_globs.log_data[0] = ':';
|
sml_dump_start('>');
|
||||||
sml_globs.log_data[1] = '>';
|
|
||||||
sml_globs.sml_logindex = 2;
|
|
||||||
for (uint16_t cnt = 0; cnt < 16; cnt++) {
|
for (uint16_t cnt = 0; cnt < 16; cnt++) {
|
||||||
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", fbuff[index++]);
|
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", mp->sbuff[index++]);
|
||||||
if (sml_globs.sml_logindex < SML_DUMP_SIZE - 7) {
|
if (sml_globs.sml_logindex < sml_globs.logsize - 7) {
|
||||||
sml_globs.sml_logindex += 3;
|
sml_globs.sml_logindex += 3;
|
||||||
}
|
}
|
||||||
logsiz--;
|
logsiz--;
|
||||||
|
@ -627,13 +641,24 @@ void dump2log(void) {
|
||||||
}
|
}
|
||||||
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
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) {
|
if (sml_globs.sml_logindex > 2) {
|
||||||
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
|
||||||
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
||||||
|
sml_dump_start(' ');
|
||||||
}
|
}
|
||||||
|
mp->hp->len = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -655,12 +680,9 @@ void dump2log(void) {
|
||||||
hcnt++;
|
hcnt++;
|
||||||
if (hcnt > 15) {
|
if (hcnt > 15) {
|
||||||
// line complete, build asci chars
|
// line complete, build asci chars
|
||||||
sml_globs.log_data[index] = '=';
|
sml_globs.log_data[index++] = '=';
|
||||||
index++;
|
sml_globs.log_data[index++] = '>';
|
||||||
sml_globs.log_data[index] = '>';
|
sml_globs.log_data[index++] = ' ';
|
||||||
index++;
|
|
||||||
sml_globs.log_data[index] = ' ';
|
|
||||||
index++;
|
|
||||||
for (uint8_t ccnt = 0; ccnt < 16; ccnt++) {
|
for (uint8_t ccnt = 0; ccnt < 16; ccnt++) {
|
||||||
if (isprint(dchars[ccnt])) {
|
if (isprint(dchars[ccnt])) {
|
||||||
sml_globs.log_data[index] = dchars[ccnt];
|
sml_globs.log_data[index] = dchars[ccnt];
|
||||||
|
@ -690,14 +712,12 @@ void dump2log(void) {
|
||||||
if (sml_globs.sml_logindex > 2) {
|
if (sml_globs.sml_logindex > 2) {
|
||||||
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
||||||
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
||||||
sml_globs.log_data[0] = ':';
|
sml_dump_start(' ');
|
||||||
sml_globs.log_data[1] = ' ';
|
|
||||||
sml_globs.sml_logindex = 2;
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sml_globs.log_data[sml_globs.sml_logindex] = c;
|
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++;
|
sml_globs.sml_logindex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -710,12 +730,10 @@ void dump2log(void) {
|
||||||
if (c == VBUS_SYNC) {
|
if (c == VBUS_SYNC) {
|
||||||
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
||||||
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
||||||
sml_globs.log_data[0] = ':';
|
sml_dump_start(' ');
|
||||||
sml_globs.log_data[1] = ' ';
|
|
||||||
sml_globs.sml_logindex = 2;
|
|
||||||
}
|
}
|
||||||
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c);
|
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;
|
sml_globs.sml_logindex += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -727,7 +745,7 @@ void dump2log(void) {
|
||||||
while (SML_SAVAILABLE) {
|
while (SML_SAVAILABLE) {
|
||||||
c = SML_SREAD;
|
c = SML_SREAD;
|
||||||
if (c == EBUS_SYNC) {
|
if (c == EBUS_SYNC) {
|
||||||
p = SML_SPEAK;
|
p = SML_SPEEK;
|
||||||
if (p != EBUS_SYNC && sml_globs.sml_logindex > 5) {
|
if (p != EBUS_SYNC && sml_globs.sml_logindex > 5) {
|
||||||
// new packet, plot last one
|
// new packet, plot last one
|
||||||
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
||||||
|
@ -738,7 +756,7 @@ void dump2log(void) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c);
|
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;
|
sml_globs.sml_logindex += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -752,12 +770,10 @@ void dump2log(void) {
|
||||||
if (c == SML_SYNC) {
|
if (c == SML_SYNC) {
|
||||||
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
sml_globs.log_data[sml_globs.sml_logindex] = 0;
|
||||||
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
||||||
sml_globs.log_data[0] = ':';
|
sml_dump_start(' ');
|
||||||
sml_globs.log_data[1] = ' ';
|
|
||||||
sml_globs.sml_logindex = 2;
|
|
||||||
}
|
}
|
||||||
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c);
|
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;
|
sml_globs.sml_logindex += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -766,16 +782,18 @@ void dump2log(void) {
|
||||||
default:
|
default:
|
||||||
// raw dump
|
// raw dump
|
||||||
d_lastms = millis();
|
d_lastms = millis();
|
||||||
sml_globs.log_data[0] = ':';
|
sml_dump_start(' ');
|
||||||
sml_globs.log_data[1] = ' ';
|
|
||||||
sml_globs.sml_logindex = 2;
|
|
||||||
while ((millis() - d_lastms) < 40) {
|
while ((millis() - d_lastms) < 40) {
|
||||||
while (SML_SAVAILABLE) {
|
while (SML_SAVAILABLE) {
|
||||||
|
d_lastms = millis();
|
||||||
|
yield();
|
||||||
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", SML_SREAD);
|
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;
|
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);
|
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1158,28 +1177,25 @@ void sml_shift_in(uint32_t meters, uint32_t shard) {
|
||||||
|
|
||||||
#ifdef USE_SML_DECRYPT
|
#ifdef USE_SML_DECRYPT
|
||||||
if (mp->use_crypt) {
|
if (mp->use_crypt) {
|
||||||
uint8_t iob = (uint8_t)mp->meter_ss->read();
|
if (mp->hp) {
|
||||||
if (mp->spos < mp->sbsiz) {
|
uint32_t timediff = millis() - mp->lastms;
|
||||||
mp->sbuff[mp->spos] = iob;
|
if (timediff > mp->tout_ms) {
|
||||||
|
mp->hp->len = 0;
|
||||||
|
mp->spos = 0;
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("SML: sync"));
|
||||||
}
|
}
|
||||||
mp->spos++;
|
mp->lastms = millis();
|
||||||
|
uint16_t len;
|
||||||
if (iob == SML_CRYPT_SYNC2 && mp->last_iob == SML_CRYPT_SYNC1) {
|
uint8_t *payload;
|
||||||
// frame start
|
if (mp->hp->readHanPort(&payload, &len)) {
|
||||||
mp->spos = 2;
|
if (len > mp->sbsiz) {
|
||||||
mp->sbuff[0] = SML_CRYPT_SYNC1;
|
len = mp->sbsiz;
|
||||||
mp->sbuff[1] = SML_CRYPT_SYNC2;
|
|
||||||
}
|
}
|
||||||
mp->last_iob = iob;
|
memmove(mp->sbuff, payload, len);
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR(">> decrypted block: %d bytes"), len);
|
||||||
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);
|
SML_Decode(meters);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1220,11 +1236,19 @@ void sml_shift_in(uint32_t meters, uint32_t shard) {
|
||||||
break;
|
break;
|
||||||
case 'R':
|
case 'R':
|
||||||
// raw without shift
|
// 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->sbuff[mp->spos] = iob;
|
||||||
mp->spos++;
|
mp->spos++;
|
||||||
if (mp->spos > mp->sbsiz) {
|
if (mp->spos > mp->sbsiz) {
|
||||||
mp->spos = 0;
|
mp->spos = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'k':
|
case 'k':
|
||||||
// Kamstrup
|
// Kamstrup
|
||||||
|
@ -1338,6 +1362,8 @@ void sml_shift_in(uint32_t meters, uint32_t shard) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t sml_count = 0;
|
||||||
|
|
||||||
// polled every 50 ms
|
// polled every 50 ms
|
||||||
void SML_Poll(void) {
|
void SML_Poll(void) {
|
||||||
uint32_t meters;
|
uint32_t meters;
|
||||||
|
@ -1352,6 +1378,14 @@ uint32_t meters;
|
||||||
while (meter_desc[meters].meter_ss->available()) {
|
while (meter_desc[meters].meter_ss->available()) {
|
||||||
sml_shift_in(meters, 0);
|
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;
|
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) {
|
void SML_Decode(uint8_t index) {
|
||||||
const char *mp = (const char*)sml_globs.meter_p;
|
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_Immediate_MQTT((const char*)mp, vindex, mindex);
|
||||||
sml_globs.dvalid[vindex] = 1;
|
sml_globs.dvalid[vindex] = 1;
|
||||||
// get sfac
|
// get sfac
|
||||||
} else if (*mp=='d') {
|
} else if (*mp == 'd') {
|
||||||
// calc deltas d ind 10 (eg every 10 secs)
|
// calc deltas d ind 10 (eg every 10 secs)
|
||||||
if (dindex < MAX_DVARS) {
|
if (dindex < MAX_DVARS) {
|
||||||
// only n indexes
|
// only n indexes
|
||||||
|
@ -1583,12 +1665,73 @@ void SML_Decode(uint8_t index) {
|
||||||
uint8_t val = hexnibble(*mp++) << 4;
|
uint8_t val = hexnibble(*mp++) << 4;
|
||||||
val |= hexnibble(*mp++);
|
val |= hexnibble(*mp++);
|
||||||
if (val != *cp++) {
|
if (val != *cp++) {
|
||||||
found=0;
|
found = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ebus modbus pzem vbus or raw
|
// ebus modbus pzem vbus or raw
|
||||||
// XXHHHHSSUU
|
if (!strncmp(mp, "pm(", 3)) {
|
||||||
if (*mp == 'x') {
|
// 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') {
|
if (*(mp + 1) == 'x') {
|
||||||
//ignore one byte
|
//ignore one byte
|
||||||
mp += 2;
|
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] = *cp++;
|
||||||
}
|
}
|
||||||
meter_desc[mindex].meter_id[p] = 0;
|
meter_desc[mindex].meter_id[p] = 0;
|
||||||
|
|
||||||
} else if (sml_globs.mp[mindex].type == 'k') {
|
} else if (sml_globs.mp[mindex].type == 'k') {
|
||||||
// 220901
|
// 220901
|
||||||
uint32_t date = mbus_dval;
|
uint32_t date = mbus_dval;
|
||||||
|
@ -1889,7 +2031,7 @@ void SML_Decode(uint8_t index) {
|
||||||
} else {
|
} else {
|
||||||
double dval;
|
double dval;
|
||||||
char type = sml_globs.mp[mindex].type;
|
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
|
// get numeric values
|
||||||
if (type == 'o' || type == 'c') {
|
if (type == 'o' || type == 'c') {
|
||||||
if (*mp == '(') {
|
if (*mp == '(') {
|
||||||
|
@ -2220,7 +2362,6 @@ void SML_Show(boolean json) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef USE_DOMOTICZ
|
#ifdef USE_DOMOTICZ
|
||||||
if (json && !TasmotaGlobal.tele_period) {
|
if (json && !TasmotaGlobal.tele_period) {
|
||||||
char str[16];
|
char str[16];
|
||||||
|
@ -2274,7 +2415,6 @@ void IRAM_ATTR SML_CounterIsr(void *arg) {
|
||||||
sml_counter_pinstate ^= (1 << index);
|
sml_counter_pinstate ^= (1 << index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifndef METER_DEF_SIZE
|
#ifndef METER_DEF_SIZE
|
||||||
#define METER_DEF_SIZE 3000
|
#define METER_DEF_SIZE 3000
|
||||||
#endif
|
#endif
|
||||||
|
@ -2336,161 +2476,169 @@ bool Gpio_used(uint8_t gpiopin) {
|
||||||
#define SML_MINSB 64
|
#define SML_MINSB 64
|
||||||
char *SpecOptions(char *cp, uint32_t mnum) {
|
char *SpecOptions(char *cp, uint32_t mnum) {
|
||||||
// special option
|
// special option
|
||||||
|
struct METER_DESC *mp = &meter_desc[mnum];
|
||||||
switch (*cp) {
|
switch (*cp) {
|
||||||
case '1':
|
case '1':
|
||||||
cp++;
|
cp++;
|
||||||
#ifdef USE_SML_SPECOPT
|
#ifdef USE_SML_SPECOPT
|
||||||
if (*cp == ',') {
|
if (*cp == ',') {
|
||||||
cp++;
|
cp++;
|
||||||
meter_desc[mnum].so_obis1 = strtol(cp, &cp, 16);
|
mp->so_obis1 = strtol(cp, &cp, 16);
|
||||||
}
|
}
|
||||||
if (*cp == ',') {
|
if (*cp == ',') {
|
||||||
cp++;
|
cp++;
|
||||||
meter_desc[mnum].so_fcode1 = strtol(cp, &cp, 16);
|
mp->so_fcode1 = strtol(cp, &cp, 16);
|
||||||
}
|
}
|
||||||
if (*cp == ',') {
|
if (*cp == ',') {
|
||||||
cp++;
|
cp++;
|
||||||
meter_desc[mnum].so_bpos1 = strtol(cp, &cp, 10);
|
mp->so_bpos1 = strtol(cp, &cp, 10);
|
||||||
}
|
}
|
||||||
if (*cp == ',') {
|
if (*cp == ',') {
|
||||||
cp++;
|
cp++;
|
||||||
meter_desc[mnum].so_fcode2 = strtol(cp, &cp, 16);
|
mp->so_fcode2 = strtol(cp, &cp, 16);
|
||||||
}
|
}
|
||||||
if (*cp == ',') {
|
if (*cp == ',') {
|
||||||
cp++;
|
cp++;
|
||||||
meter_desc[mnum].so_bpos2 = strtol(cp, &cp, 10);
|
mp->so_bpos2 = strtol(cp, &cp, 10);
|
||||||
}
|
}
|
||||||
if (*cp == ',') {
|
if (*cp == ',') {
|
||||||
cp++;
|
cp++;
|
||||||
meter_desc[mnum].so_obis2 = strtol(cp, &cp, 16);
|
mp->so_obis2 = strtol(cp, &cp, 16);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case '2':
|
case '2':
|
||||||
cp += 2;
|
cp += 2;
|
||||||
meter_desc[mnum].so_flags = strtol(cp, &cp, 16);
|
mp->so_flags = strtol(cp, &cp, 16);
|
||||||
break;
|
break;
|
||||||
case '3':
|
case '3':
|
||||||
cp += 2;
|
cp += 2;
|
||||||
meter_desc[mnum].sbsiz = strtol(cp, &cp, 10);
|
mp->sbsiz = strtol(cp, &cp, 10);
|
||||||
if (*cp == ',') {
|
if (*cp == ',') {
|
||||||
cp++;
|
cp++;
|
||||||
meter_desc[mnum].sibsiz = strtol(cp, &cp, 10);
|
mp->sibsiz = strtol(cp, &cp, 10);
|
||||||
if (meter_desc[mnum].sibsiz < SML_MINSB) {
|
if (mp->sibsiz < SML_MINSB) {
|
||||||
meter_desc[mnum].sibsiz = SML_MINSB;
|
mp->sibsiz = SML_MINSB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (*cp == ',') {
|
||||||
|
cp++;
|
||||||
|
sml_globs.logsize = strtol(cp, &cp, 10);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case '4':
|
case '4':
|
||||||
cp += 2;
|
cp += 2;
|
||||||
#ifdef USE_SML_DECRYPT
|
#ifdef USE_SML_DECRYPT
|
||||||
meter_desc[mnum].use_crypt = true;
|
meter_desc[mnum].use_crypt = true;
|
||||||
for (uint8_t cnt = 0; cnt < (SML_CRYPT_SIZE * 2); cnt += 2) {
|
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;
|
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;
|
return cp;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_SML_DECRYPT
|
#ifdef USE_SML_DECRYPT
|
||||||
|
uint16_t serial_dispatch(uint8_t meter, uint8_t sel) {
|
||||||
//// calculate crc16 CCITT
|
struct METER_DESC *mp = &meter_desc[meter];
|
||||||
uint16_t hdlc_crc16(const uint8_t *dp, uint8_t len) {
|
if (!sel) {
|
||||||
#define POLY 0x8408
|
return mp->meter_ss->available();
|
||||||
uint8_t i;
|
|
||||||
uint16_t data;
|
|
||||||
uint16_t crc = 0xffff;
|
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
return (~crc);
|
|
||||||
}
|
}
|
||||||
do {
|
uint8_t iob = mp->meter_ss->read();
|
||||||
data = (unsigned int)0xff & *dp++;
|
return iob;
|
||||||
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 *hdlc_decode(uint8_t *data, uint32_t dlen, uint8_t *key, uint16_t *size) {
|
int SML_print(const char *format, ...) {
|
||||||
if (dlen < 31) {
|
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;
|
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);
|
||||||
// crc OK
|
AddLog(LOG_LEVEL_DEBUG, PSTR("SML: %s"),temp);
|
||||||
uint8_t ivec[12];
|
va_end(arg);
|
||||||
uint8_t index = 0;
|
if (len >= sizeof(loc_buf)) {
|
||||||
for (uint8_t cnt = 14; cnt < 14+8; cnt++) {
|
free(temp);
|
||||||
ivec[index] = data[cnt];
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
for (uint8_t cnt = 24; cnt < 24+4; cnt++) {
|
return len;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif // USE_SML_DECRYPT
|
||||||
|
|
||||||
void reset_sml_vars(uint16_t maxmeters) {
|
void reset_sml_vars(uint16_t maxmeters) {
|
||||||
|
|
||||||
for (uint32_t meters = 0; meters < maxmeters; meters++) {
|
for (uint32_t meters = 0; meters < maxmeters; meters++) {
|
||||||
meter_desc[meters].spos = 0;
|
|
||||||
meter_desc[meters].sbsiz = SML_BSIZ;
|
struct METER_DESC *mp = &meter_desc[meters];
|
||||||
meter_desc[meters].sibsiz = TMSBSIZ;
|
mp->spos = 0;
|
||||||
if (meter_desc[meters].sbuff) {
|
mp->sbsiz = SML_BSIZ;
|
||||||
free(meter_desc[meters].sbuff);
|
mp->sibsiz = TMSBSIZ;
|
||||||
meter_desc[meters].sbuff = 0;
|
if (mp->sbuff) {
|
||||||
|
free(mp->sbuff);
|
||||||
|
mp->sbuff = 0;
|
||||||
}
|
}
|
||||||
#ifdef USE_SML_SPECOPT
|
#ifdef USE_SML_SPECOPT
|
||||||
meter_desc[meters].so_obis1 = 0;
|
mp->so_obis1 = 0;
|
||||||
meter_desc[meters].so_obis2 = 0;
|
mp->so_obis2 = 0;
|
||||||
#endif
|
#endif
|
||||||
meter_desc[meters].so_flags = 0;
|
mp->so_flags = 0;
|
||||||
// addresses a bug in meter DWS74
|
// addresses a bug in meter DWS74
|
||||||
#ifdef DWS74_BUG
|
#ifdef DWS74_BUG
|
||||||
meter_desc[meters].so_flags |= SO_DWS74_BUG;
|
mp->so_flags |= SO_DWS74_BUG;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SML_OBIS_LINE
|
#ifdef SML_OBIS_LINE
|
||||||
meter_desc[meters].so_flags |= SO_OBIS_LINE;
|
mp->so_flags |= SO_OBIS_LINE;
|
||||||
#endif
|
#endif
|
||||||
if (meter_desc[meters].txmem) {
|
if (mp->txmem) {
|
||||||
free(meter_desc[meters].txmem);
|
free(mp->txmem);
|
||||||
meter_desc[meters].txmem = 0;
|
mp->txmem = 0;
|
||||||
}
|
}
|
||||||
meter_desc[meters].txmem = 0;
|
mp->txmem = 0;
|
||||||
meter_desc[meters].trxpin = -1;
|
mp->trxpin = -1;
|
||||||
if (meter_desc[meters].meter_ss) {
|
if (mp->meter_ss) {
|
||||||
delete meter_desc[meters].meter_ss;
|
delete mp->meter_ss;
|
||||||
meter_desc[meters].meter_ss = NULL;
|
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 {
|
} else {
|
||||||
mp->shift_mode = (type != 'o' && type != 'e' && type != 'k' && type != 'm' && type != 'M' && type != 'p' && type != 'R' && type != 'v');
|
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;
|
sml_globs.ready = true;
|
||||||
|
@ -3462,6 +3620,7 @@ bool XSNS_53_cmd(void) {
|
||||||
char *cp = XdrvMailbox.data;
|
char *cp = XdrvMailbox.data;
|
||||||
if (*cp == 'd') {
|
if (*cp == 'd') {
|
||||||
// set dump mode
|
// set dump mode
|
||||||
|
if (sml_globs.ready) {
|
||||||
cp++;
|
cp++;
|
||||||
uint8_t index = atoi(cp);
|
uint8_t index = atoi(cp);
|
||||||
if ((index & 7) > sml_globs.meters_used) index = 1;
|
if ((index & 7) > sml_globs.meters_used) index = 1;
|
||||||
|
@ -3474,10 +3633,11 @@ bool XSNS_53_cmd(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index > 0) {
|
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;
|
sml_globs.dump2log = index;
|
||||||
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"dump: %d\"}}"), sml_globs.dump2log);
|
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"dump: %d\"}}"), sml_globs.dump2log);
|
||||||
|
}
|
||||||
} else if (*cp == 'c') {
|
} else if (*cp == 'c') {
|
||||||
// set counter
|
// set counter
|
||||||
cp++;
|
cp++;
|
||||||
|
@ -3498,38 +3658,38 @@ bool XSNS_53_cmd(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"counter%d: %d\"}}"), index, RtcSettings.pulse_counter[index - 1]);
|
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"counter%d: %d\"}}"), index, RtcSettings.pulse_counter[index - 1]);
|
||||||
} else if (*cp=='r') {
|
} else if (*cp == 'r') {
|
||||||
// restart
|
// restart
|
||||||
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"restart\"}}"));
|
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"restart\"}}"));
|
||||||
SML_CounterSaveState();
|
SML_CounterSaveState();
|
||||||
SML_Init();
|
SML_Init();
|
||||||
} else if (*cp=='m') {
|
} else if (*cp == 'm') {
|
||||||
// meter number for serial activity
|
// meter number for serial activity
|
||||||
cp++;
|
cp++;
|
||||||
if (!isdigit(*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 {
|
} else {
|
||||||
sml_globs.ser_act_meter_num=atoi(cp);
|
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);
|
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
|
// serial activity LED-GPIO
|
||||||
cp++;
|
cp++;
|
||||||
if (!isdigit(*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 {
|
} 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)) {
|
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);
|
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;
|
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);
|
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 {
|
} else {
|
||||||
serviced=false;
|
serviced = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return serviced;
|
return serviced;
|
||||||
|
|
Loading…
Reference in New Issue