* 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:
gemu 2023-01-30 15:03:46 +01:00 committed by GitHub
parent 9e9afe88f1
commit f09a083777
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1972 additions and 228 deletions

25
lib/lib_div/ams/Cosem.cpp Normal file
View File

@ -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;
}

92
lib/lib_div/ams/Cosem.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -0,0 +1,6 @@
#include "LlcParser.h"
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
ctx.length -= 3;
return 3;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

321
lib/lib_div/ams/Time.cpp Normal file
View File

@ -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;
}

144
lib/lib_div/ams/TimeLib.h Normal file
View File

@ -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 */

29
lib/lib_div/ams/crc.cpp Normal file
View File

@ -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;
}

10
lib/lib_div/ams/crc.h Normal file
View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

@ -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" ]
}
}

View File

@ -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

View File

@ -0,0 +1,5 @@
#include "ntohll.h"
uint64_t ntohll(uint64_t x) {
return (((uint64_t)ntohl((uint32_t)x)) << 32) + ntohl(x >> 32);
}

8
lib/lib_div/ams/ntohll.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef _NTOHLL_H
#define _NTOHLL_H
#include "lwip/def.h"
uint64_t ntohll(uint64_t x);
#endif

View File

@ -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;
} }

View File

@ -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;