Tasmota/tasmota/tasmota_xsns_sensor/xsns_53_sml.ino

3786 lines
104 KiB
C++
Executable File

/*
xsns_53_sml.ino - SML,OBIS,EBUS,RAW,COUNTER interface for Tasmota
Created by Gerhard Mutz on 07.10.11.
adapted for Tasmota
Copyright (C) 2021 Gerhard Mutz and Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_SML_M
#define XSNS_53 53
// this driver depends on use USE_SCRIPT !!!
// debug counter input to led for counter1 and 2
//#define DEBUG_CNT_LED1 2
//#define DEBUG_CNT_LED1 2
#include <TasmotaSerial.h>
// use special no wait serial driver, should be always on
#ifndef ESP32
#define SPECIAL_SS
#endif
// max number of meters , may be adjusted
#ifndef MAX_METERS
#define MAX_METERS 5
#endif
/* additional defines
USE_ESP32_SW_SERIAL
default off, uses a special combo driver that allows more then 3 serial ports on ESP32.
define rec pins as negativ to use software serial
USE_SML_AUTHKEY
rarely used , thus off by default
*/
// if you have to save more RAM you may disable these options by defines in user_config_override
#ifndef NO_SML_REPLACE_VARS
// allows to replace values in decoder section with script string variables
#undef SML_REPLACE_VARS
#define SML_REPLACE_VARS
#endif
#ifndef NO_USE_SML_SPECOPT
// allows to define special option 1 for meters that use a direction bit
#undef USE_SML_SPECOPT
#define USE_SML_SPECOPT
#endif
#ifndef NO_USE_SML_SCRIPT_CMD
// allows several sml cmds from scripts, as well as access to sml registers
#undef USE_SML_SCRIPT_CMD
#define USE_SML_SCRIPT_CMD
#endif
#ifndef NO_USE_SML_DECRYPT
// allows 256 bit AES decryption
#define USE_SML_DECRYPT
#endif
// median filter eliminates outliers, but uses much RAM and CPU cycles
// 672 bytes extra RAM with SML_MAX_VARS = 16
// default compile on, but must be enabled by descriptor flag 16
// may be undefined if RAM must be saved
#ifndef NO_USE_SML_MEDIAN_FILTER
#undef USE_SML_MEDIAN_FILTER
#define USE_SML_MEDIAN_FILTER
#endif
#ifdef USE_SML_DECRYPT
#include "han_Parser.h"
#endif
/* special options per meter
1:
special binary SML option for meters that use a bit in the status register to sign import or export like ED300L, AS2020 or DTZ541
a. obis code that holds the direction bit,
b. Flag identifier,
c. direction bit,
d. second Flag identifier (some meters use 2 different flags),
e. second bit,
f. obis code of value to be inverted on direction bit.
e.g. 1,=so1,00010800,65,11,65,11,00100700 for DTZ541
2:
flags, currently only bit 0 and 1
if 1 fix DWS74 bug
if 2 use asci obis line compare instead a pattern compare
e.g. 1,=so2,2 set obis line mode on meter 1
3:
serial buffers
a. serial buffer size
b. serial irq buffer size, a must be given
c. dumplog buffer size, default is 128 , a and b must be given
e.g. 1,=so3,256,256 set serial buffers on meter 1
4:
decrytion key, 16 bytes hex btw 32 chars without spaces or commas
defining key switches decryption mode on
5:
authentication key, 16 bytes hex btw 32 chars without spaces or commas
needs USE_SML_AUTHKEY
6:
synchronisation timout in milliseconds, after no serial data within this
time serial pointer is reset to zero
*/
//#define MODBUS_DEBUG
// ESP32 combined hardware and software serial driver, software read only
#ifdef ESP32
#ifdef USE_ESP32_SW_SERIAL
#ifndef ESP32_SWS_BUFFER_SIZE
#define ESP32_SWS_BUFFER_SIZE 256
#endif
class SML_ESP32_SERIAL : public Stream {
public:
SML_ESP32_SERIAL(uint32_t uart_index);
virtual ~SML_ESP32_SERIAL();
bool begin(uint32_t speed, uint32_t smode, int32_t recpin, int32_t trxpin);
int32_t peek(void);
int read(void) override;
size_t write(uint8_t byte) override;
int available(void) override;
void flush(void) override;
void setRxBufferSize(uint32_t size);
void updateBaudRate(uint32_t baud);
void rxRead(void);
void end();
using Print::write;
private:
// Member variables
void setbaud(uint32_t speed);
uint32_t uart_index;
int8_t m_rx_pin;
int8_t m_tx_pin;
uint32_t cfgmode;
uint32_t ss_byte;
uint32_t ss_bstart;
uint32_t ss_index;
uint32_t m_bit_time;
uint32_t m_in_pos;
uint32_t m_out_pos;
uint16_t serial_buffer_size;
bool m_valid;
uint8_t *m_buffer;
HardwareSerial *hws;
};
void IRAM_ATTR sml_callRxRead(void *self) { ((SML_ESP32_SERIAL*)self)->rxRead(); };
SML_ESP32_SERIAL::SML_ESP32_SERIAL(uint32_t index) {
uart_index = index;
m_valid = true;
}
SML_ESP32_SERIAL::~SML_ESP32_SERIAL(void) {
if (hws) {
hws->end();
delete(hws);
} else {
detachInterrupt(m_rx_pin);
if (m_buffer) {
free(m_buffer);
}
}
}
void SML_ESP32_SERIAL::setbaud(uint32_t speed) {
m_bit_time = ESP.getCpuFreqMHz() * 1000000 / speed;
}
void SML_ESP32_SERIAL::end(void) {
if (m_buffer) {
free(m_buffer);
}
}
bool SML_ESP32_SERIAL::begin(uint32_t speed, uint32_t smode, int32_t recpin, int32_t trxpin) {
if (!m_valid) { return false; }
m_buffer = 0;
if (recpin < 0) {
setbaud(speed);
m_rx_pin = -recpin;
serial_buffer_size = ESP32_SWS_BUFFER_SIZE;
m_buffer = (uint8_t*)malloc(serial_buffer_size);
if (m_buffer == NULL) return false;
pinMode(m_rx_pin, INPUT_PULLUP);
attachInterruptArg(m_rx_pin, sml_callRxRead, this, CHANGE);
m_in_pos = m_out_pos = 0;
hws = nullptr;
} else {
cfgmode = smode;
m_rx_pin = recpin;
m_tx_pin = trxpin;
hws = new HardwareSerial(uart_index);
if (hws) {
hws->begin(speed, cfgmode, m_rx_pin, m_tx_pin);
}
}
return true;
}
void SML_ESP32_SERIAL::flush(void) {
if (hws) {
hws->flush();
} else {
m_in_pos = m_out_pos = 0;
}
}
int32_t SML_ESP32_SERIAL::peek(void) {
if (hws) {
return hws->peek();
} else {
if (m_in_pos == m_out_pos) return -1;
return m_buffer[m_out_pos];
}
}
int SML_ESP32_SERIAL::read(void) {
if (hws) {
return hws->read();
} else {
if (m_in_pos == m_out_pos) return -1;
uint32_t ch = m_buffer[m_out_pos];
m_out_pos = (m_out_pos + 1) % serial_buffer_size;
return ch;
}
}
int SML_ESP32_SERIAL::available(void) {
if (hws) {
return hws->available();
} else {
int avail = m_in_pos - m_out_pos;
if (avail < 0) avail += serial_buffer_size;
return avail;
}
}
size_t SML_ESP32_SERIAL::write(uint8_t byte) {
if (hws) {
return hws->write(byte);
}
return 0;
}
void SML_ESP32_SERIAL::setRxBufferSize(uint32_t size) {
if (hws) {
hws->setRxBufferSize(size);
} else {
if (m_buffer) {
free(m_buffer);
}
serial_buffer_size = size;
m_buffer = (uint8_t*)malloc(size);
}
}
void SML_ESP32_SERIAL::updateBaudRate(uint32_t baud) {
if (hws) {
hws->updateBaudRate(baud);
} else {
setbaud(baud);
}
}
// no wait mode only 8N1 (or 7X1, obis only, ignoring parity)
void IRAM_ATTR SML_ESP32_SERIAL::rxRead(void) {
uint32_t diff;
uint32_t level;
#define SML_LASTBIT 9
level = digitalRead(m_rx_pin);
if (!level && !ss_index) {
// start condition
ss_bstart = ESP.getCycleCount() - (m_bit_time / 4);
ss_byte = 0;
ss_index++;
} else {
// now any bit changes go here
// calc bit number
diff = (ESP.getCycleCount() - ss_bstart) / m_bit_time;
if (!level && diff > SML_LASTBIT) {
// start bit of next byte, store and restart
// leave irq at change
for (uint32_t i = ss_index; i <= SML_LASTBIT; i++) {
ss_byte |= (1 << i);
}
uint32_t next = (m_in_pos + 1) % serial_buffer_size;
if (next != (uint32_t)m_out_pos) {
m_buffer[m_in_pos] = ss_byte >> 1;
m_in_pos = next;
}
ss_bstart = ESP.getCycleCount() - (m_bit_time / 4);
ss_byte = 0;
ss_index = 1;
return;
}
if (diff >= SML_LASTBIT) {
// bit zero was 0,
uint32_t next = (m_in_pos + 1) % serial_buffer_size;
if (next != (uint32_t)m_out_pos) {
m_buffer[m_in_pos] = ss_byte >> 1;
m_in_pos = next;
}
ss_byte = 0;
ss_index = 0;
} else {
// shift in
for (uint32_t i = ss_index; i < diff; i++) {
if (!level) ss_byte |= (1 << i);
}
ss_index = diff;
}
}
}
#endif // USE_ESP32_SW_SERIAL
#endif // ESP32
typedef union {
uint8_t data;
struct {
uint8_t trxenpol : 1; // string or number
uint8_t trxen : 1;
uint8_t trxenpin : 6;
};
} TRX_EN_TYPE;
#ifndef TMSBSIZ
#define TMSBSIZ 256
#endif
#ifndef SML_STIMEOUT
#define SML_STIMEOUT 1000
#endif
#define SO_DWS74_BUG 1
#define SO_OBIS_LINE 2
#define METER_ID_SIZE 24
#define SML_CRYPT_SIZE 16
struct METER_DESC {
int8_t srcpin;
uint8_t type;
uint16_t flag;
int32_t params;
char prefix[8];
int8_t trxpin;
uint8_t tsecs;
char *txmem;
uint8_t index;
uint8_t max_index;
char *script_str;
uint8_t sopt;
TRX_EN_TYPE trx_en;
bool shift_mode;
uint16_t sbsiz;
uint8_t *sbuff;
uint16_t spos;
uint16_t sibsiz;
uint32_t lastms;
uint16_t tout_ms;
uint8_t so_flags;
char meter_id[METER_ID_SIZE];
#ifdef USE_SML_SPECOPT
uint32_t so_obis1;
uint32_t so_obis2;
uint8_t so_fcode1;
uint8_t so_bpos1;
uint8_t so_fcode2;
uint8_t so_bpos2;
#endif
#ifdef ESP32
#ifndef USE_ESP32_SW_SERIAL
HardwareSerial *meter_ss;
#else
SML_ESP32_SERIAL *meter_ss;
#endif
#endif // ESP32
// software serial pointers
#ifdef ESP8266
TasmotaSerial *meter_ss;
#endif // ESP8266
#ifdef USE_SML_DECRYPT
bool use_crypt = false;
uint8_t last_iob;
uint8_t key[SML_CRYPT_SIZE];
Han_Parser *hp;
#ifdef USE_SML_AUTHKEY
uint8_t auth[SML_CRYPT_SIZE];
#endif
#endif
};
struct METER_DESC meter_desc[MAX_METERS];
// this driver uses double because some meter vars would not fit in float
//=====================================================
// serial buffers, may be made larger depending on telegram lenght
#ifndef SML_BSIZ
#define SML_BSIZ 48
#endif
#define VBUS_SYNC 0xaa
#define SML_SYNC 0x77
#define EBUS_SYNC 0xaa
#define EBUS_ESC 0xa9
// calulate deltas
#define MAX_DVARS MAX_METERS*2
#ifndef SML_DUMP_SIZE
#define SML_DUMP_SIZE 128
#endif
// median filter, should be odd size
#define MEDIAN_SIZE 5
struct SML_MEDIAN_FILTER {
double buffer[MEDIAN_SIZE];
int8_t index;
};
struct SML_GLOBS {
uint8_t sml_send_blocks;
uint8_t sml_100ms_cnt;
uint8_t sml_desc_cnt;
uint8_t meters_used;
uint8_t maxvars;
uint8_t *meter_p;
double *meter_vars;
uint8_t *dvalid;
double dvalues[MAX_DVARS];
uint32_t dtimes[MAX_DVARS];
char sml_start;
uint8_t dump2log = 0;
uint8_t ser_act_LED_pin = 255;
uint8_t ser_act_meter_num = 0;
uint16_t sml_logindex;
char *log_data;
uint16_t logsize = SML_DUMP_SIZE;
#if defined(ED300L) || defined(AS2020) || defined(DTZ541) || defined(USE_SML_SPECOPT)
uint8_t sml_status[MAX_METERS];
uint8_t g_mindex;
#endif
#ifdef USE_SML_MEDIAN_FILTER
struct SML_MEDIAN_FILTER *sml_mf;
#endif
uint8_t *script_meter;
struct METER_DESC *mp;
bool ready;
} sml_globs;
#define SML_OPTIONS_JSON_ENABLE 1
uint8_t sml_options = SML_OPTIONS_JSON_ENABLE;
#ifdef USE_SML_MEDIAN_FILTER
#ifndef FLT_MAX
#define FLT_MAX 99999999
#endif
double sml_median_array(double *array, uint8_t len) {
uint8_t ind[len];
uint8_t mind = 0, index = 0, flg;
double min = FLT_MAX;
for (uint8_t hcnt = 0; hcnt < len / 2 + 1; hcnt++) {
for (uint8_t mcnt = 0; mcnt < len; mcnt++) {
flg = 0;
for (uint8_t icnt = 0; icnt < index; icnt++) {
if (ind[icnt] == mcnt) {
flg = 1;
}
}
if (!flg) {
if (array[mcnt] < min) {
min = array[mcnt];
mind = mcnt;
}
}
}
ind[index] = mind;
index++;
min = FLT_MAX;
}
return array[ind[len / 2]];
}
// calc median
double sml_median(struct SML_MEDIAN_FILTER* mf, double in) {
//double tbuff[MEDIAN_SIZE],tmp;
//uint8_t flag;
mf->buffer[mf->index] = in;
mf->index++;
if (mf->index >= MEDIAN_SIZE) mf->index = 0;
return sml_median_array(mf->buffer, MEDIAN_SIZE);
/*
// sort list and take median
memmove(tbuff,mf->buffer,sizeof(tbuff));
for (byte ocnt=0; ocnt<MEDIAN_SIZE; ocnt++) {
flag=0;
for (byte count=0; count<MEDIAN_SIZE-1; count++) {
if (tbuff[count]>tbuff[count+1]) {
tmp=tbuff[count];
tbuff[count]=tbuff[count+1];
tbuff[count+1]=tmp;
flag=1;
}
}
if (!flag) break;
}
return tbuff[MEDIAN_SIZE/2];
*/
}
#endif
#define SML_SAVAILABLE Serial_available()
#define SML_SREAD Serial_read()
#define SML_SPEEK Serial_peek()
uint16_t Serial_available() {
uint8_t num = sml_globs.dump2log & 7;
if (num < 1 || num > sml_globs.meters_used) num = 1;
if (!meter_desc[num - 1].meter_ss) return 0;
return meter_desc[num - 1].meter_ss->available();
}
uint8_t Serial_read() {
uint8_t num = sml_globs.dump2log & 7;
if (num < 1 || num > sml_globs.meters_used) num = 1;
if (!meter_desc[num - 1].meter_ss) return 0;
return meter_desc[num - 1].meter_ss->read();
}
uint8_t Serial_peek() {
uint8_t num = sml_globs.dump2log & 7;
if (num < 1 || num > sml_globs.meters_used) num = 1;
if (!meter_desc[num - 1].meter_ss) return 0;
return meter_desc[num - 1].meter_ss->peek();
}
void sml_dump_start(char c) {
sml_globs.log_data[0] = ':';
sml_globs.log_data[1] = c;
sml_globs.sml_logindex = 2;
}
#define SML_EBUS_SKIP_SYNC_DUMPS
uint8_t *hdlc_decode(struct METER_DESC *mp, uint16_t *size);
void dump2log(void) {
int16_t index = 0, hcnt = 0;
uint32_t d_lastms;
uint8_t dchars[16];
uint8_t meter = (sml_globs.dump2log & 7) - 1;
uint8_t type = sml_globs.mp[meter].type;
//if (!SML_SAVAILABLE) return;
if (!sml_globs.log_data) return;
#ifdef USE_SML_DECRYPT
struct METER_DESC *mp = &meter_desc[meter];
if (mp->use_crypt == true) {
d_lastms = millis();
while ((millis() - d_lastms) < 50) {
while (SML_SAVAILABLE) {
d_lastms = millis();
uint16_t logsiz;
uint8_t *payload;
if (mp->hp->readHanPort(&payload, &logsiz)) {
if (logsiz > mp->sbsiz) {
logsiz = mp->sbsiz;
}
memmove(mp->sbuff, payload, logsiz);
AddLog(LOG_LEVEL_INFO, PSTR("SML: decrypted block: %d bytes"), logsiz);
uint16_t index = 0;
while (logsiz) {
sml_dump_start('>');
for (uint16_t cnt = 0; cnt < 16; cnt++) {
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", mp->sbuff[index++]);
if (sml_globs.sml_logindex < sml_globs.logsize - 7) {
sml_globs.sml_logindex += 3;
}
logsiz--;
if (!logsiz) {
break;
}
}
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) {
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
sml_dump_start(' ');
}
mp->hp->len = 0;
return;
}
#endif
if (sml_globs.dump2log & 8) {
// combo mode
while (SML_SAVAILABLE) {
sml_globs.log_data[index] = ':';
index++;
sml_globs.log_data[index] = ' ';
index++;
d_lastms = millis();
while ((millis() - d_lastms) < 40) {
if (SML_SAVAILABLE) {
uint8_t c = SML_SREAD;
sprintf(&sml_globs.log_data[index], "%02x ", c);
dchars[hcnt] = c;
index += 3;
hcnt++;
if (hcnt > 15) {
// line complete, build asci chars
sml_globs.log_data[index++] = '=';
sml_globs.log_data[index++] = '>';
sml_globs.log_data[index++] = ' ';
for (uint8_t ccnt = 0; ccnt < 16; ccnt++) {
if (isprint(dchars[ccnt])) {
sml_globs.log_data[index] = dchars[ccnt];
} else {
sml_globs.log_data[index] = ' ';
}
index++;
}
break;
}
}
}
if (index > 0) {
sml_globs.log_data[index] = 0;
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
index = 0;
hcnt = 0;
}
}
} else {
switch (type) {
case 'o':
// obis
while (SML_SAVAILABLE) {
char c = SML_SREAD&0x7f;
if (c == '\n' || c == '\r') {
if (sml_globs.sml_logindex > 2) {
sml_globs.log_data[sml_globs.sml_logindex] = 0;
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
sml_dump_start(' ');
}
continue;
}
sml_globs.log_data[sml_globs.sml_logindex] = c;
if (sml_globs.sml_logindex < sml_globs.logsize - 2) {
sml_globs.sml_logindex++;
}
}
break;
case 'v':
// vbus
{ uint8_t c;
while (SML_SAVAILABLE) {
c = SML_SREAD;
if (c == VBUS_SYNC) {
sml_globs.log_data[sml_globs.sml_logindex] = 0;
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
sml_dump_start(' ');
}
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c);
if (sml_globs.sml_logindex < sml_globs.logsize - 7) {
sml_globs.sml_logindex += 3;
}
}
}
break;
case 'e':
// ebus
{ uint8_t c, p;
while (SML_SAVAILABLE) {
c = SML_SREAD;
if (c == EBUS_SYNC) {
p = SML_SPEEK;
if (p != EBUS_SYNC && sml_globs.sml_logindex > 5) {
// new packet, plot last one
sml_globs.log_data[sml_globs.sml_logindex] = 0;
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
strcpy(&sml_globs.log_data[0], ": aa ");
sml_globs.sml_logindex = 5;
}
continue;
}
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c);
if (sml_globs.sml_logindex < sml_globs.logsize - 7) {
sml_globs.sml_logindex += 3;
}
}
}
break;
case 's':
// sml
{ uint8_t c;
while (SML_SAVAILABLE) {
c = SML_SREAD;
if (c == SML_SYNC) {
sml_globs.log_data[sml_globs.sml_logindex] = 0;
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
sml_dump_start(' ');
}
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", c);
if (sml_globs.sml_logindex < sml_globs.logsize - 7) {
sml_globs.sml_logindex += 3;
}
}
}
break;
default:
// raw dump
d_lastms = millis();
sml_dump_start(' ');
while ((millis() - d_lastms) < 40) {
while (SML_SAVAILABLE) {
d_lastms = millis();
yield();
sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x ", SML_SREAD);
if (sml_globs.sml_logindex < sml_globs.logsize - 7) {
sml_globs.sml_logindex += 3;
}
if (sml_globs.sml_logindex >= 32*3+2) {
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
sml_dump_start(' ');
}
}
}
if (sml_globs.sml_logindex > 2) {
sml_globs.log_data[sml_globs.sml_logindex] = 0;
AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
}
break;
}
}
}
void Hexdump(uint8_t *sbuff, uint32_t slen) {
char cbuff[slen*3+10];
char *cp = cbuff;
*cp++ = '>';
*cp++ = ' ';
for (uint32_t cnt = 0; cnt < slen; cnt ++) {
sprintf(cp, "%02x ", sbuff[cnt]);
cp += 3;
}
AddLogData(LOG_LEVEL_INFO, cbuff);
}
#define DOUBLE2CHAR dtostrfd
// skip sml entries
uint8_t *skip_sml(uint8_t *cp,int16_t *res) {
uint8_t len,len1,type;
len=*cp&0xf;
type=*cp&0x70;
if (type==0x70) {
// list, skip entries
// list
cp++;
while (len--) {
len1=*cp&0x0f;
cp+=len1;
}
*res=0;
} else {
// skip len
*res=(signed char)*(cp+1);
cp+=len;
}
return cp;
}
// get sml binary value
// not defined for unsigned >0x7fff ffff ffff ffff (should never happen)
double sml_getvalue(unsigned char *cp, uint8_t index) {
uint8_t len,unit,type;
int16_t scaler,result;
int64_t value;
double dval;
// scan for values
// check status
#ifdef ED300L
unsigned char *cpx=cp-5;
// decode OBIS 0180 amd extract direction info
if (*cp==0x64 && *cpx==0 && *(cpx+1)==0x01 && *(cpx+2)==0x08 && *(cpx+3)==0) {
sml_globs.sml_status[sml_globs.g_mindex]=*(cp+3);
}
if (*cp==0x63 && *cpx==0 && *(cpx+1)==0x01 && *(cpx+2)==0x08 && *(cpx+3)==0) {
sml_globs.sml_status[sml_globs.g_mindex]=*(cp+2);
}
#endif
#ifdef AS2020
unsigned char *cpx=cp-5;
// decode OBIS 0180 amd extract direction info
if (*cp==0x64 && *cpx==0 && *(cpx+1)==0x01 && *(cpx+2)==0x08 && *(cpx+3)==0) {
sml_globs.sml_status[sml_globs.g_mindex]=*(cp+2);
}
if (*cp==0x63 && *cpx==0 && *(cpx+1)==0x01 && *(cpx+2)==0x08 && *(cpx+3)==0) {
sml_globs.sml_status[sml_globs.g_mindex]=*(cp+1);
}
#endif
#ifdef DTZ541
unsigned char *cpx=cp-5;
// decode OBIS 0180 amd extract direction info
if (*cp==0x65 && *cpx==0 && *(cpx+1)==0x01 && *(cpx+2)==0x08 && *(cpx+3)==0) {
sml_globs.sml_status[sml_globs.g_mindex]=*(cp+3);
}
#endif
#ifdef USE_SML_SPECOPT
unsigned char *cpx = cp - 5;
uint32_t ocode = (*(cpx + 0) << 24) | (*(cpx + 1) << 16) | (*(cpx + 2) << 8) | (*(cpx + 3) << 0);
if (ocode == meter_desc[sml_globs.g_mindex].so_obis1) {
sml_globs.sml_status[sml_globs.g_mindex] &= 0xfe;
uint32_t flag = 0;
uint16_t bytes = 0;
if (*cp == meter_desc[sml_globs.g_mindex].so_fcode1) {
cpx = cp + 1;
bytes = (meter_desc[sml_globs.g_mindex].so_fcode1 & 0xf) - 1;
for (uint16_t cnt = 0; cnt < bytes; cnt++) {
flag <<= 8;
flag |= *cpx++;
}
if (flag & (1 << meter_desc[sml_globs.g_mindex].so_bpos1)) {
sml_globs.sml_status[sml_globs.g_mindex] |= 1;
}
}
if (*cp == meter_desc[sml_globs.g_mindex].so_fcode2) {
cpx = cp + 1;
bytes = (meter_desc[sml_globs.g_mindex].so_fcode2 & 0xf) - 1;
for (uint16_t cnt = 0; cnt < bytes; cnt++) {
flag <<= 8;
flag |= *cpx++;
}
if (flag & (1 << meter_desc[sml_globs.g_mindex].so_bpos1)) {
sml_globs.sml_status[sml_globs.g_mindex] |= 1;
}
}
}
#endif
cp = skip_sml(cp, &result);
// check time
cp = skip_sml(cp, &result);
// check unit
cp = skip_sml(cp, &result);
// check scaler
cp = skip_sml(cp, &result);
scaler = result;
// get value
type = *cp & 0x70;
len = *cp & 0x0f;
cp++;
if (type == 0x50 || type == 0x60) {
// shift into 64 bit
uint64_t uvalue = 0;
uint8_t nlen = len;
while (--nlen) {
uvalue <<= 8;
uvalue |= *cp++;
}
if (type == 0x50) {
// signed
switch (len - 1) {
case 1:
// byte
value = (signed char)uvalue;
break;
case 2:
// signed 16 bit
if (meter_desc[index].so_flags & SO_DWS74_BUG) {
if (scaler == -2) {
value = (uint32_t)uvalue;
} else {
value = (int16_t)uvalue;
}
} else {
value = (int16_t)uvalue;
}
break;
case 3:
// signed 24 bit
value = (int32_t)(uvalue << 8);
value /= 256;
break;
case 4:
// signed 32 bit
value = (int32_t)uvalue;
break;
case 5:
case 6:
case 7:
case 8:
// signed 64 bit
value = (int64_t)uvalue;
break;
}
} else {
// unsigned
value = uvalue;
}
} else {
if (!(type & 0xf0)) {
// octet string serial number
// no coding found on the net
// up to now 2 types identified on Hager
if (len == 9) {
// serial number on hager => 24 bit - 24 bit
cp++;
uint32_t s1,s2;
s1 = *cp << 16 | *(cp + 1) <<8 | *(cp + 2);
cp += 4;
s2 = *cp << 16 | *(cp + 1) <<8 | *(cp + 2);
sprintf(&meter_desc[index].meter_id[0], "%u-%u", s1, s2);
} else {
// server id on hager
char *str = &meter_desc[index].meter_id[0];
for (type = 0; type < len - 1; type++) {
sprintf(str,"%02x", *cp++);
str += 2;
}
}
value = 0;
} else {
value = 999999;
scaler = 0;
}
}
dval = value;
if (scaler == -1) {
dval /= 10;
} else if (scaler == -2) {
dval /= 100;
} else if (scaler == -3) {
dval /= 1000;
} else if (scaler == -4) {
dval /= 10000;
} else if (scaler == 1) {
dval *= 10;
} else if (scaler == 2) {
dval *= 100;
} else if (scaler == 3) {
dval *= 1000;
}
#ifdef ED300L
// decode current power OBIS 00 0F 07 00
if (*cpx==0x00 && *(cpx+1)==0x0f && *(cpx+2)==0x07 && *(cpx+3)==0) {
if (sml_globs.sml_status[sml_globs.g_mindex]&0x20) {
// and invert sign on solar feed
dval*=-1;
}
}
#endif
#ifdef AS2020
// decode current power OBIS 00 10 07 00
if (*cpx==0x00 && *(cpx+1)==0x10 && *(cpx+2)==0x07 && *(cpx+3)==0) {
if (sml_globs.sml_status[sml_globs.g_mindex]&0x08) {
// and invert sign on solar feed
dval*=-1;
}
}
#endif
#ifdef DTZ541
// decode current power OBIS 00 10 07 00
if (*cpx==0x00 && *(cpx+1)==0x10 && *(cpx+2)==0x07 && *(cpx+3)==0) {
if (sml_globs.sml_status[sml_globs.g_mindex]&0x08) {
// and invert sign on solar feed
dval*=-1;
}
}
#endif
#ifdef USE_SML_SPECOPT
ocode = (*(cpx + 0) << 24) | (*(cpx + 1) << 16) | (*(cpx + 2) << 8) | (*(cpx + 3) << 0);
if (ocode == meter_desc[sml_globs.g_mindex].so_obis2) {
if (sml_globs.sml_status[sml_globs.g_mindex] & 1) {
// and invert sign on solar feed
dval *= -1;
}
}
#endif
return dval;
}
uint8_t hexnibble(char chr) {
uint8_t rVal = 0;
if (isdigit(chr)) {
rVal = chr - '0';
} else {
chr=toupper(chr);
if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A';
}
return rVal;
}
uint8_t sb_counter;
// need double precision in this driver
double CharToDouble(const char *str)
{
// simple ascii to double, because atof or strtod are too large
char strbuf[24];
strlcpy(strbuf, str, sizeof(strbuf));
char *pt = strbuf;
while ((*pt != '\0') && isspace(*pt)) { pt++; } // Trim leading spaces
signed char sign = 1;
if (*pt == '-') { sign = -1; }
if (*pt == '-' || *pt=='+') { pt++; } // Skip any sign
double left = 0;
if (*pt != '.') {
left = atoll(pt); // Get left part
while (isdigit(*pt)) { pt++; } // Skip number
}
double right = 0;
if (*pt == '.') {
pt++;
right = atoll(pt); // Decimal part
while (isdigit(*pt)) {
pt++;
right /= 10.0;
}
}
double result = left + right;
if (sign < 0) {
return -result; // Add negative sign
}
return result;
}
// remove ebus escapes
void ebus_esc(uint8_t *ebus_buffer, unsigned char len) {
short count,count1;
for (count = 0; count < len; count++) {
if (ebus_buffer[count] == EBUS_ESC) {
//found escape
ebus_buffer[count] += ebus_buffer[count + 1];
// remove 2. char
count++;
for (count1 = count; count1 < len; count1++) {
ebus_buffer[count1] = ebus_buffer[count1 + 1];
}
}
}
}
uint8_t ebus_crc8(uint8_t data, uint8_t crc_init) {
uint8_t crc;
uint8_t polynom;
int i;
crc = crc_init;
for (i = 0; i < 8; i++) {
if (crc & 0x80) {
polynom = (uint8_t) 0x9B;
}
else {
polynom = (uint8_t) 0;
}
crc = (uint8_t)((crc & ~0x80) << 1);
if (data & 0x80) {
crc = (uint8_t)(crc | 1) ;
}
crc = (uint8_t)(crc ^ polynom);
data = (uint8_t)(data << 1);
}
return (crc);
}
// ebus crc
uint8_t ebus_CalculateCRC( uint8_t *Data, uint16_t DataLen ) {
uint16_t i;
uint8_t Crc = 0;
for(i = 0 ; i < DataLen ; ++i, ++Data ) {
Crc = ebus_crc8( *Data, Crc );
}
return Crc;
}
void sml_empty_receiver(uint32_t meters) {
while (meter_desc[meters].meter_ss->available()) {
meter_desc[meters].meter_ss->read();
}
}
void sml_shift_in(uint32_t meters, uint32_t shard) {
uint32_t count;
struct METER_DESC *mp = &meter_desc[meters];
if (!mp->sbuff) return;
#ifdef USE_SML_DECRYPT
if (mp->use_crypt) {
if (mp->hp) {
uint32_t timediff = millis() - mp->lastms;
if (timediff > mp->tout_ms) {
mp->hp->len = 0;
mp->spos = 0;
AddLog(LOG_LEVEL_DEBUG, PSTR("SML: sync"));
}
mp->lastms = millis();
uint16_t len;
uint8_t *payload;
if (mp->hp->readHanPort(&payload, &len)) {
if (len > mp->sbsiz) {
len = mp->sbsiz;
}
memmove(mp->sbuff, payload, len);
AddLog(LOG_LEVEL_DEBUG, PSTR("SML: decrypted block: %d bytes"), len);
SML_Decode(meters);
}
}
return;
}
#endif
if (mp->shift_mode) {
// shift in
for (count = 0; count < mp->sbsiz - 1; count++) {
mp->sbuff[count] = mp->sbuff[count + 1];
}
}
uint8_t iob = (uint8_t)mp->meter_ss->read();
switch (mp->type) {
case 'o':
// asci obis
if (!(mp->so_flags & SO_OBIS_LINE)) {
mp->sbuff[mp->sbsiz - 1] = iob & 0x7f;
} else {
iob &= 0x7f;
mp->sbuff[mp->spos] = iob;
mp->spos++;
if (mp->spos >= mp->sbsiz) {
mp->spos = 0;
}
if ((iob == 0x0a) || (iob == 0x0d)) {
SML_Decode(meters);
mp->spos = 0;
}
}
break;
case 's':
// binary obis = sml
mp->sbuff[mp->sbsiz - 1] = iob;
break;
case 'r':
// raw with shift
mp->sbuff[mp->sbsiz - 1] = iob;
break;
case 'R':
// raw without shift
{
uint32_t timediff = millis() - mp->lastms;
if (timediff > mp->tout_ms) {
mp->spos = 0;
AddLog(LOG_LEVEL_DEBUG, PSTR("SML: sync"));
}
mp->lastms = millis();
mp->sbuff[mp->spos] = iob;
mp->spos++;
if (mp->spos > mp->sbsiz) {
mp->spos = 0;
}
}
break;
case 'k':
// Kamstrup
if (iob == 0x40) {
mp->spos = 0;
} else if (iob == 0x0d) {
uint8_t index = 0;
uint8_t *ucp = &mp->sbuff[0];
for (uint16_t cnt = 0; cnt < mp->spos; cnt++) {
uint8_t iob = mp->sbuff[cnt] ;
if (iob == 0x1b) {
*ucp++ = mp->sbuff[cnt + 1] ^ 0xff;
cnt++;
} else {
*ucp++ = iob;
}
index++;
}
uint16_t crc = KS_calculateCRC(&mp->sbuff[0], index);
if (!crc) {
SML_Decode(meters);
}
sml_empty_receiver(meters);
mp->spos = 0;
} else {
mp->sbuff[mp->spos] = iob;
mp->spos++;
if (mp->spos >= mp->sbsiz) {
mp->spos = 0;
}
}
break;
case 'm':
case 'M':
// modbus
mp->sbuff[mp->spos] = iob;
mp->spos++;
if (mp->spos >= mp->sbsiz) {
mp->spos = 0;
}
if (mp->spos >= 3) {
uint32_t mlen = mp->sbuff[2] + 5;
if (mlen > mp->sbsiz) mlen = mp->sbsiz;
if (mp->spos >= mlen) {
#ifdef MODBUS_DEBUG
AddLog(LOG_LEVEL_INFO, PSTR("receive index >> %d"), mp->index);
Hexdump(mp->sbuff, 10);
#endif
SML_Decode(meters);
sml_empty_receiver(meters);
mp->spos = 0;
}
}
break;
case 'p':
// pzem
mp->sbuff[mp->spos] = iob;
mp->spos++;
if (mp->spos >= 7) {
SML_Decode(meters);
sml_empty_receiver(meters);
mp->spos = 0;
}
break;
case 'v':
// vbus
if (iob == EBUS_SYNC) {
sb_counter = 0;
SML_Decode(meters);
mp->sbuff[0] = iob;
mp->spos = 1;
} else {
if (mp->spos < mp->sbsiz) {
mp->sbuff[mp->spos] = iob;
mp->spos++;
}
}
break;
case 'e':
// ebus
if (iob == EBUS_SYNC) {
// should be end of telegramm
// QQ,ZZ,PB,SB,NN ..... CRC, ACK SYNC
if (mp->spos > 4 + 5) {
// get telegramm lenght
uint16_t tlen = mp->sbuff[4] + 5;
// test crc
if (mp->sbuff[tlen] = ebus_CalculateCRC(mp->sbuff, tlen)) {
ebus_esc(mp->sbuff, tlen);
SML_Decode(meters);
} else {
// crc error
//AddLog(LOG_LEVEL_INFO, PSTR("ebus crc error"));
}
}
mp->spos = 0;
return;
}
mp->sbuff[mp->spos] = iob;
mp->spos++;
if (mp->spos >= mp->sbsiz) {
mp->spos = 0;
}
break;
}
sb_counter++;
if (mp->shift_mode) {
SML_Decode(meters);
}
}
uint16_t sml_count = 0;
// polled every 50 ms
void SML_Poll(void) {
uint32_t meters;
for (meters = 0; meters < sml_globs.meters_used; meters++) {
if (sml_globs.mp[meters].type != 'c') {
// poll for serial input
if (!meter_desc[meters].meter_ss) continue;
if (sml_globs.ser_act_LED_pin != 255 && (sml_globs.ser_act_meter_num == 0 || sml_globs.ser_act_meter_num - 1 == meters)) {
digitalWrite(sml_globs.ser_act_LED_pin, meter_desc[meters].meter_ss->available() && !digitalRead(sml_globs.ser_act_LED_pin)); // Invert LED, if queue is continuously full
}
while (meter_desc[meters].meter_ss->available()) {
sml_shift_in(meters, 0);
}
/*
if (meter_desc[meters].meter_ss->available()) {
sml_count++;
uint8_t iob = meter_desc[meters].meter_ss->read();
if (sml_count<5 || sml_count > 100) {
AddLog(LOG_LEVEL_INFO, PSTR(">> %02x - %d"),iob,sml_count);
}
}*/
}
}
}
#define VBUS_BAD_CRC 0
// get vbus septet with 6 bytes
uint32_t vbus_get_septet(uint8_t *cp) {
uint32_t result = 0;
//AddLog(LOG_LEVEL_INFO,PSTR("septet: %02x %02x %02x %02x %02x %02x"),cp[0] ,cp[1],cp[2],cp[3],cp[4],cp[5]);
uint8_t Crc = 0x7F;
for (uint32_t i = 0; i < 5; i++) {
Crc = (Crc - cp[i]) & 0x7f;
}
if (Crc != cp[5]) {
result = VBUS_BAD_CRC;
} else {
result = (cp[3] | ((cp[4]&8)<<4));
result <<= 8;
result |= (cp[2] | ((cp[4]&4)<<5));
result <<= 8;
result |= (cp[1] | ((cp[4]&2)<<6));
result <<= 8;
result |= (cp[0] | ((cp[4]&1)<<7));
}
//AddLog(LOG_LEVEL_INFO,PSTR("septet r: %d"),result);
return result;
}
char *skip_double(char *cp) {
if (*cp == '+' || *cp == '-') {
cp++;
}
while (*cp) {
if (*cp == '.') {
cp++;
}
if (!isdigit(*cp)) {
return cp;
}
cp++;
}
return 0;
}
uint8_t *sml_find(uint8_t *src, uint16_t ssize, uint8_t *pattern, uint16_t psize) {
//AddLog(LOG_LEVEL_INFO, PSTR(">> %02x %02x %02x %02x"),pattern[0],pattern[1],pattern[2],pattern[3]);
if (psize >= ssize) {
return 0;
}
for (uint32_t cnt = 0; cnt < ssize - psize; cnt++) {
if (!memcmp(src, pattern, psize)) {
return src;
}
src++;
}
return 0;
}
#ifdef USE_SML_DECRYPT
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;
}
#endif // USE_SML_DECRYPT
void SML_Decode(uint8_t index) {
const char *mp = (const char*)sml_globs.meter_p;
int8_t mindex;
uint8_t *cp;
uint8_t dindex = 0, vindex = 0;
delay(0);
if (!sml_globs.ready) {
return;
}
while (mp != NULL) {
// check list of defines
if (*mp == 0) break;
// new section
mindex = ((*mp) & 7) - 1;
if (mindex < 0 || mindex >= sml_globs.meters_used) mindex = 0;
mp += 2;
if (*mp == '=' && *(mp+1) == 'h') {
mp = strchr(mp, '|');
if (mp) mp++;
continue;
}
if (*mp == '=' && *(mp+1) == 's') {
mp = strchr(mp, '|');
if (mp) mp++;
continue;
}
// =d must handle dindex
if (*mp == '=' && *(mp + 1) == 'd') {
if (index != mindex) {
dindex++;
}
}
if (index != mindex) goto nextsect;
// start of serial source buffer
cp = meter_desc[mindex].sbuff;
// compare
if (*mp == '=') {
// calculated entry, check syntax
mp++;
// do math m 1+2+3
if (*mp == 'm' && !sb_counter) {
// only every 256 th byte
// else it would be calculated every single serial byte
mp++;
while (*mp == ' ') mp++;
// 1. index
double dvar;
uint8_t opr;
uint8_t mind;
int32_t ind;
mind = strtol((char*)mp, (char**)&mp, 10);
if (mind < 1 || mind > sml_globs.maxvars) mind = 1;
dvar = sml_globs.meter_vars[mind - 1];
while (*mp==' ') mp++;
for (uint8_t p = 0; p < 8; p++) {
if (*mp == '@') {
// store result
sml_globs.meter_vars[vindex] = dvar;
mp++;
break;
}
opr = *mp;
mp++;
uint8_t iflg = 0;
if (*mp == '#') {
iflg = 1;
mp++;
}
ind = strtol((char*)mp, (char**)&mp, 10);
mind = ind;
if (mind < 1 || mind > sml_globs.maxvars) mind = 1;
switch (opr) {
case '+':
if (iflg) dvar += ind;
else dvar += sml_globs.meter_vars[mind - 1];
break;
case '-':
if (iflg) dvar -= ind;
else dvar -= sml_globs.meter_vars[mind - 1];
break;
case '*':
if (iflg) dvar *= ind;
else dvar *= sml_globs.meter_vars[mind - 1];
break;
case '/':
if (iflg) dvar /= ind;
else dvar /= sml_globs.meter_vars[mind - 1];
break;
}
while (*mp==' ') mp++;
if (*mp == '@') {
// store result
sml_globs.meter_vars[vindex] = dvar;
mp++;
break;
}
}
double fac = CharToDouble((char*)mp);
sml_globs.meter_vars[vindex] /= fac;
SML_Immediate_MQTT((const char*)mp, vindex, mindex);
sml_globs.dvalid[vindex] = 1;
// get sfac
} else if (*mp == 'd') {
// calc deltas d ind 10 (eg every 10 secs)
if (dindex < MAX_DVARS) {
// only n indexes
mp++;
while (*mp == ' ') mp++;
uint8_t ind = atoi(mp);
while (*mp >= '0' && *mp <= '9') mp++;
if (ind < 1 || ind > sml_globs.maxvars) ind = 1;
uint32_t delay = atoi(mp) * 1000;
uint32_t dtime = millis() - sml_globs.dtimes[dindex];
if (dtime > delay) {
// calc difference
sml_globs.dtimes[dindex] = millis();
double vdiff = sml_globs.meter_vars[ind - 1] - sml_globs.dvalues[dindex];
sml_globs.dvalues[dindex] = sml_globs.meter_vars[ind - 1];
double dres = (double)360000.0 * vdiff / ((double)dtime / 10000.0);
sml_globs.dvalid[vindex] += 1;
if (sml_globs.dvalid[vindex] >= 2) {
// differece is only valid after 2. calculation
sml_globs.dvalid[vindex] = 2;
#ifdef USE_SML_MEDIAN_FILTER
if (sml_globs.mp[mindex].flag & 16) {
sml_globs.meter_vars[vindex] = sml_median(&sml_globs.sml_mf[vindex], dres);
} else {
sml_globs.meter_vars[vindex] = dres;
}
#else
sml_globs.meter_vars[vindex] = dres;
#endif
}
mp=strchr(mp,'@');
if (mp) {
mp++;
double fac = CharToDouble((char*)mp);
sml_globs.meter_vars[vindex] /= fac;
SML_Immediate_MQTT((const char*)mp, vindex, mindex);
}
}
//sml_globs.dvalid[vindex] = 1;
dindex++;
}
} else if (*mp == 'h') {
// skip html tag line
mp = strchr(mp, '|');
if (mp) mp++;
continue;
} else if (*mp == 's') {
// skip spec option tag line
mp = strchr(mp, '|');
if (mp) mp++;
continue;
}
} else {
// compare value
uint8_t found = 1;
double ebus_dval = 99;
double mbus_dval = 99;
while (*mp != '@') {
if (sml_globs.mp[mindex].type == 'o' || sml_globs.mp[mindex].type == 'c') {
if (*mp++ != *cp++) {
found=0;
}
} else {
if (sml_globs.mp[mindex].type == 's') {
// sml
uint8_t val = hexnibble(*mp++) << 4;
val |= hexnibble(*mp++);
if (val != *cp++) {
found = 0;
}
} else {
// ebus modbus pzem vbus or raw
if (!strncmp(mp, "pm(", 3)) {
// pattern match
uint8_t dp = 0;
mp += 3;
// default to asci obis
uint8_t aflg = 3;
if (*mp == 'r') {
aflg = 0;
mp++;
} else if (*mp == 'h') {
aflg = 1;
mp++;
}
uint8_t pattern[64];
// check for obis pattern
for (uint32_t cnt = 0; cnt < sizeof(pattern); cnt++) {
if (*mp == '@' || !*mp) {
break;
}
if (*mp == ')') {
mp++;
if ((aflg & 2) && (dp == 2)) {
pattern[cnt] = 0xff;
cnt++;
}
pattern[cnt] = 0;
uint8_t *ucp = sml_find(cp, meter_desc[index].sbsiz, pattern, cnt);
if (ucp) {
cp = ucp + cnt;
// check auto type
if (aflg & 1) {
// METER_ID_SIZE
#ifdef USE_SML_DECRYPT
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);
}
#endif
}
}
break;
}
uint8_t iob;
if (aflg & 2) {
iob = strtol((char*)mp, (char**)&mp, 10);
if (*mp == '.') {
mp++;
dp++;
}
} else {
iob = hexnibble(*mp++) << 4;
iob |= hexnibble(*mp++);
}
pattern[cnt] = iob;
}
} else if (*mp == 'x') {
if (*(mp + 1) == 'x') {
//ignore one byte
mp += 2;
cp++;
} else {
mp++;
if (isdigit(*mp)) {
uint8_t skip = strtol((char*)mp, (char**)&mp, 10);
cp += skip;
}
}
} else if (!strncmp(mp, "UUuuUUuu", 8)) {
uint32_t val = (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | (cp[3]<<0);
mp += 8;
cp += 4;
if (*mp == 's') {
mp++;
// swap words
val = (val>>16) | (val<<16);
}
ebus_dval = val;
mbus_dval = val;
} else if (!strncmp(mp, "uuUUuuUU", 8)) {
uint32_t val = (cp[1]<<24) | (cp[0]<<16) | (cp[3]<<8) | (cp[2]<<0);
mp += 8;
cp += 4;
if (*mp == 's') {
mp++;
// swap words
val = (val>>16) | (val<<16);
}
ebus_dval = val;
mbus_dval = val;
} else if (!strncmp(mp, "UUuu", 4)) {
uint16_t val = cp[1] | (cp[0]<<8);
mbus_dval = val;
ebus_dval = val;
mp += 4;
cp += 2;
} else if (!strncmp(mp, "SSssSSss", 8)) {
int32_t val = (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | (cp[3]<<0);
mp += 8;
cp += 4;
if (*mp == 's') {
mp++;
// swap words
val = ((uint32_t)val>>16) | ((uint32_t)val<<16);
}
ebus_dval = val;
mbus_dval = val;
} else if (!strncmp(mp, "ssSSssSS", 8)) {
int32_t val = (cp[1]<<24) | (cp[0]<<16) | (cp[3]<<8) | (cp[2]<<0);
mp += 8;
cp += 4;
if (*mp == 's') {
mp++;
// swap words
val = ((uint32_t)val>>16) | ((uint32_t)val<<16);
}
ebus_dval = val;
mbus_dval = val;
} else if (!strncmp(mp, "uuUU", 4)) {
uint16_t val = cp[0] | (cp[1]<<8);
mbus_dval = val;
ebus_dval = val;
mp += 4;
cp += 2;
} else if (!strncmp(mp, "uu", 2)) {
uint8_t val = *cp++;
mbus_dval = val;
ebus_dval = val;
mp += 2;
} else if (!strncmp(mp, "ssSS", 4)) {
int16_t val = *cp | (*(cp+1)<<8);
mbus_dval = val;
ebus_dval = val;
mp += 4;
cp += 2;
} else if (!strncmp(mp, "SSss", 4)) {
int16_t val = cp[1] | (cp[0]<<8);
mbus_dval = val;
ebus_dval = val;
mp += 4;
cp += 2;
} else if (!strncmp(mp,"ss", 2)) {
int8_t val = *cp++;
mbus_dval = val;
ebus_dval = val;
mp += 2;
} else if (!strncmp(mp, "ffffffff", 8)) {
uint32_t val = (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | (cp[3]<<0);
float *fp = (float*)&val;
ebus_dval = *fp;
mbus_dval = *fp;
mp += 8;
cp += 4;
} else if (!strncmp(mp, "FFffFFff", 8)) {
// reverse word float
uint32_t val = (cp[1]<<0) | (cp[0]<<8) | (cp[3]<<16) | (cp[2]<<24);
float *fp = (float*)&val;
ebus_dval = *fp;
mbus_dval = *fp;
mp += 8;
cp += 4;
} else if (!strncmp(mp, "eeeeee", 6)) {
uint32_t val = (cp[0]<<16) | (cp[1]<<8) | (cp[2]<<0);
mbus_dval = val;
mp += 6;
cp += 3;
} else if (!strncmp(mp, "vvvvvv", 6)) {
mbus_dval = (float)((cp[0]<<8) | (cp[1])) + ((float)cp[2]/10.0);
mp += 6;
cp += 3;
} else if (!strncmp(mp, "cccccc", 6)) {
mbus_dval = (float)((cp[0]<<8) | (cp[1])) + ((float)cp[2]/100.0);
mp += 6;
cp += 3;
} else if (!strncmp(mp, "pppp", 4)) {
mbus_dval = (float)((cp[0]<<8) | cp[1]);
mp += 4;
cp += 2;
} else if (!strncmp(mp, "kstr", 4)) {
mp += 4;
// decode the mantissa
uint32_t x = 0;
for (uint16_t i = 0; i < cp[1]; i++) {
x <<= 8;
x |= cp[i + 3];
}
// decode the exponent
int32_t i = cp[2] & 0x3f;
if (cp[2] & 0x40) {
i = -i;
};
//float ifl = pow(10, i);
float ifl = 1;
for (uint16_t x = 1; x <= i; ++x) {
ifl *= 10;
}
if (cp[2] & 0x80) {
ifl = -ifl;
}
mbus_dval = (double )(x * ifl);
} else if (!strncmp(mp, "bcd", 3)) {
mp += 3;
uint8_t digits = strtol((char*)mp, (char**)&mp, 10);
if (digits < 2) digits = 2;
if (digits > 12) digits = 12;
uint64_t bcdval = 0;
uint64_t mfac = 1;
for (uint32_t cnt = 0; cnt < digits; cnt += 2) {
uint8_t iob = *cp++;
bcdval += (iob & 0xf) * mfac;
mfac *= 10;
bcdval += (iob >> 4) * mfac;
mfac *= 10;
}
mbus_dval = bcdval;
ebus_dval = bcdval;
} else if (*mp == 'v') {
// vbus values vul, vsl, vuwh, vuwl, wswh, vswl, vswh
// vub3, vsb3 etc
mp++;
int16_t offset = -1;
if (*mp == 'o') {
mp++;
offset = strtol((char*)mp, (char**)&mp, 10);
cp += (offset / 4) * 6;
}
uint8_t usign;
if (*mp == 'u') {
usign = 1;
} else if (*mp == 's') {
usign = 0;
}
mp++;
switch (*mp) {
case 'l':
mp++;
// get long value
if (usign) {
ebus_dval = vbus_get_septet(cp);
} else {
ebus_dval = (int32_t)vbus_get_septet(cp);
}
break;
case 'w':
mp++;
char wflg;
if (offset >= 0) {
if (offset % 4) {
wflg = 'h';
} else {
wflg = 'l';
}
} else {
wflg = *mp;
mp++;
}
// get word value
if (wflg == 'h') {
// high word
if (usign) ebus_dval = (vbus_get_septet(cp) >> 16) & 0xffff;
else ebus_dval = (int16_t)((vbus_get_septet(cp) >> 16) & 0xffff);
} else {
// low word
if (usign) ebus_dval = vbus_get_septet(cp) & 0xffff;
else ebus_dval = (int16_t)(vbus_get_septet(cp) & 0xffff);
}
break;
case 'b':
mp++;
char bflg;
if (offset >= 0) {
bflg = 0x30 | (offset % 4);
} else {
bflg = *mp;
mp++;
}
switch (bflg) {
case '3':
if (usign) ebus_dval = vbus_get_septet(cp) >> 24;
else ebus_dval = (int8_t)(vbus_get_septet(cp) >> 24);
break;
case '2':
if (usign) ebus_dval = (vbus_get_septet(cp) >> 16) & 0xff;
else ebus_dval = (int8_t)((vbus_get_septet(cp) >> 16) & 0xff);
break;
case '1':
if (usign) ebus_dval = (vbus_get_septet(cp) >> 8) & 0xff;
else ebus_dval = (int8_t)((vbus_get_septet(cp) >> 8) & 0xff);
break;
case '0':
if (usign) ebus_dval = vbus_get_septet(cp) & 0xff;
else ebus_dval = (int8_t)(vbus_get_septet(cp) & 0xff);
break;
}
break;
case 't':
mp++;
{ uint16_t time;
if (offset % 4) {
time = (vbus_get_septet(cp) >> 16) & 0xffff;
} else {
time = vbus_get_septet(cp) & 0xffff;
}
sprintf(&meter_desc[index].meter_id[0], "%02d:%02d", time / 60, time % 60);
}
break;
}
cp += 6;
}
else {
uint8_t val = hexnibble(*mp++) << 4;
val |= hexnibble(*mp++);
if (val != *cp++) {
found = 0;
}
}
}
}
}
if (found) {
// matches, get value
sml_globs.dvalid[vindex] = 1;
mp++;
#if defined(ED300L) || defined(AS2020) || defined(DTZ541) || defined(USE_SML_SPECOPT)
sml_globs.g_mindex = mindex;
#endif
if (*mp == '#') {
// get string value
getstr:
mp++;
if (sml_globs.mp[mindex].type != 'v') {
if (sml_globs.mp[mindex].type == 'o') {
uint32_t p;
for (p = 0; p < METER_ID_SIZE - 2; p++) {
if (*cp == *mp) {
break;
}
meter_desc[mindex].meter_id[p] = *cp++;
}
meter_desc[mindex].meter_id[p] = 0;
} else if (sml_globs.mp[mindex].type == 'k') {
// 220901
uint32_t date = mbus_dval;
uint8_t year = date / 10000; // = 22
date -= year * 10000;
uint8_t month = date / 100; // = 09
uint8_t day = date % 100; // = 01
sprintf(&meter_desc[mindex].meter_id[0],"%02d.%02d.%02d",day, month, year);
} else {
sml_getvalue(cp, mindex);
}
}
} else {
double dval;
char type = sml_globs.mp[mindex].type;
if (type != 'e' && type != 'r' && type != 'R' && type != 'm' && type != 'M' && type != 'k' && type != 'p' && type != 'v') {
// get numeric values
if (type == 'o' || type == 'c') {
if (*mp == '(') {
mp++;
// skip this number of brackets
uint8_t toskip = strtol((char*)mp,(char**)&mp, 10);
mp++;
char *lcp = (char*)cp;
if (toskip) {
char *bp = (char*)cp;
for (uint32_t cnt = 0; cnt < toskip; cnt++) {
bp = strchr(bp, '(');
if (!bp) {
break;
}
bp++;
lcp = bp;
}
}
if (*mp == '#') {
cp = (uint8_t*)lcp;
goto getstr;
}
dval = CharToDouble((char*)lcp);
} else {
dval = CharToDouble((char*)cp);
}
} else {
dval = sml_getvalue(cp, mindex);
}
} else {
// ebus pzem vbus or mbus or raw
if (*mp == 'b') {
mp++;
uint8_t shift = *mp&7;
ebus_dval = (uint32_t)ebus_dval >> shift;
ebus_dval = (uint32_t)ebus_dval & 1;
mp+=2;
}
if (*mp == 'i') {
// mbus index
mp++;
uint8_t mb_index = strtol((char*)mp, (char**)&mp, 10);
if (mb_index != sml_globs.mp[mindex].index) {
goto nextsect;
}
if (sml_globs.mp[mindex].type == 'k') {
// crc is already checked, get float value
dval = mbus_dval;
mp++;
} else {
uint16_t pos = meter_desc[mindex].sbuff[2] + 3;
if (pos > (meter_desc[mindex].sbsiz - 2)) pos = meter_desc[mindex].sbsiz - 2;
uint16_t crc = MBUS_calculateCRC(&meter_desc[mindex].sbuff[0], pos, 0xFFFF);
if (lowByte(crc) != meter_desc[mindex].sbuff[pos]) goto nextsect;
if (highByte(crc) != meter_desc[mindex].sbuff[pos + 1]) goto nextsect;
dval = mbus_dval;
//AddLog(LOG_LEVEL_INFO, PSTR(">> %s"),mp);
mp++;
}
} else {
if (sml_globs.mp[mindex].type == 'p') {
uint8_t crc = SML_PzemCrc(&meter_desc[mindex].sbuff[0],6);
if (crc != meter_desc[mindex].sbuff[6]) goto nextsect;
dval = mbus_dval;
} else {
dval = ebus_dval;
}
}
}
#ifdef USE_SML_MEDIAN_FILTER
if (sml_globs.mp[mindex].flag & 16) {
sml_globs.meter_vars[vindex] = sml_median(&sml_globs.sml_mf[vindex], dval);
} else {
sml_globs.meter_vars[vindex] = dval;
}
#else
sml_globs.meter_vars[vindex] = dval;
#endif
//AddLog(LOG_LEVEL_INFO, PSTR(">> %s"),mp);
// get scaling factor
double fac = CharToDouble((char*)mp);
// get optional offset to calibrate meter
char *cp = skip_double((char*)mp);
if (cp && (*cp == '+' || *cp == '-')) {
double offset = CharToDouble(cp);
sml_globs.meter_vars[vindex] += offset;
}
sml_globs.meter_vars[vindex] /= fac;
SML_Immediate_MQTT((const char*)mp, vindex, mindex);
}
}
//AddLog(LOG_LEVEL_INFO, PSTR("set valid in line %d"), vindex);
}
nextsect:
// next section
if (vindex < sml_globs.maxvars - 1) {
vindex++;
}
mp = strchr(mp, '|');
if (mp) mp++;
}
}
//"1-0:1.8.0*255(@1," D_TPWRIN ",kWh," DJ_TPWRIN ",4|"
void SML_Immediate_MQTT(const char *mp,uint8_t index,uint8_t mindex) {
char tpowstr[32];
char jname[24];
// we must skip sf,webname,unit
char *cp = strchr(mp, ',');
if (cp) {
cp++;
// wn
cp = strchr(cp,',');
if (cp) {
cp++;
// unit
cp = strchr(cp,',');
if (cp) {
cp++;
// json mqtt
for (uint8_t count = 0; count < sizeof(jname); count++) {
if (*cp == ',') {
jname[count] = 0;
break;
}
jname[count] = *cp++;
}
cp++;
uint8_t dp = atoi(cp);
if (dp & 0x10) {
// immediate mqtt
DOUBLE2CHAR(sml_globs.meter_vars[index], dp & 0xf, tpowstr);
ResponseTime_P(PSTR(",\"%s\":{\"%s\":%s}}"), sml_globs.mp[mindex].prefix, jname, tpowstr);
MqttPublishTeleSensor();
}
}
}
}
}
// web + json interface
void SML_Show(boolean json) {
int8_t count, mindex, cindex = 0;
char tpowstr[32];
char name[24];
char unit[8];
char jname[24];
int8_t index = 0, mid = 0;
char *mp = (char*)sml_globs.meter_p;
char *cp, nojson = 0;
//char b_mqtt_data[MESSZ];
//b_mqtt_data[0]=0;
if (!sml_globs.meters_used) return;
int8_t lastmind = ((*mp) & 7) - 1;
if (lastmind < 0 || lastmind >= sml_globs.meters_used) lastmind = 0;
while (mp != NULL) {
if (*mp == 0) break;
// setup sections
mindex = ((*mp) & 7) - 1;
if (mindex < 0 || mindex >= sml_globs.meters_used) mindex = 0;
if (sml_globs.mp[mindex].prefix[0] == '*' && sml_globs.mp[mindex].prefix[1] == 0) {
nojson = 1;
} else {
nojson = 0;
}
mp += 2;
if (*mp == '=' && *(mp + 1) == 'h') {
mp += 2;
// html tag
if (json) {
mp = strchr(mp, '|');
if (mp) mp++;
continue;
}
// web ui export
uint8_t i;
for (i = 0; i < sizeof(tpowstr) - 2; i++) {
if (*mp == '|' || *mp == 0) break;
tpowstr[i] = *mp++;
}
tpowstr[i] = 0;
// export html
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s{s}%s{e}", b_mqtt_data,tpowstr);
WSContentSend_PD(PSTR("{s}%s{e}"), tpowstr);
// rewind, to ensure strchr
mp--;
mp = strchr(mp, '|');
if (mp) mp++;
continue;
}
if (*mp == '=' && *(mp + 1) == 's') {
mp = strchr(mp, '|');
if (mp) mp++;
continue;
}
// skip compare section
cp = strchr(mp, '@');
if (cp) {
cp++;
tststr:
if (*cp == '#') {
// meter id
if (*(cp + 1) == 'x') {
// convert hex to asci
sml_hex_asci(mindex, tpowstr);
} else {
sprintf(tpowstr,"\"%s\"", &meter_desc[mindex].meter_id[0]);
}
mid = 1;
} else if (*cp == '(') {
if (sml_globs.mp[mindex].type == 'o') {
cp++;
strtol((char*)cp,(char**)&cp, 10);
cp++;
goto tststr;
} else {
mid = 0;
}
} else if (*cp == 'b') {
// bit value
#ifdef SML_BIT_TEXT
sprintf_P(tpowstr, PSTR("\"%s\""), (uint8_t)sml_globs.meter_vars[index]?D_ON:D_OFF);
mid = 2;
#endif
} else {
mid = 0;
}
// skip scaling
cp = strchr(cp, ',');
if (cp) {
// this is the name in web UI
cp++;
for (count = 0; count < sizeof(name); count++) {
if (*cp == ',') {
name[count] = 0;
break;
}
name[count] = *cp++;
}
cp++;
for (count = 0; count < sizeof(unit); count++) {
if (*cp == ',') {
unit[count] = 0;
break;
}
unit[count] = *cp++;
}
cp++;
for (count = 0; count < sizeof(jname); count++) {
if (*cp == ',') {
jname[count] = 0;
break;
}
jname[count] = *cp++;
}
cp++;
if (!mid) {
uint8_t dp = atoi(cp) & 0xf;
DOUBLE2CHAR(sml_globs.meter_vars[index], dp, tpowstr);
}
if (json) {
//if (sml_globs.dvalid[index]) {
//AddLog(LOG_LEVEL_INFO, PSTR("not yet valid line %d"), index);
//}
// json export
if (index == 0) {
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s,\"%s\":{\"%s\":%s", b_mqtt_data,sml_globs.mp[mindex].prefix,jname,tpowstr);
if (!nojson) {
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%s"), sml_globs.mp[mindex].prefix, jname, tpowstr);
}
}
else {
if (lastmind != mindex) {
// meter changed, close mqtt
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s}", b_mqtt_data);
if (!nojson) {
ResponseAppend_P(PSTR("}"));
}
// and open new
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s,\"%s\":{\"%s\":%s", b_mqtt_data,sml_globs.mp[mindex].prefix,jname,tpowstr);
if (!nojson) {
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%s"), sml_globs.mp[mindex].prefix, jname, tpowstr);
}
lastmind = mindex;
} else {
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s,\"%s\":%s", b_mqtt_data,jname,tpowstr);
if (!nojson) {
ResponseAppend_P(PSTR(",\"%s\":%s"), jname, tpowstr);
}
}
}
} else {
// web ui export
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s{s}%s %s: {m}%s %s{e}", b_mqtt_data,meter_desc[mindex].prefix,name,tpowstr,unit);
if (strcmp(name, "*")) WSContentSend_PD(PSTR("{s}%s %s {m}%s %s{e}"), sml_globs.mp[mindex].prefix, name,tpowstr, unit);
}
}
}
if (index < sml_globs.maxvars - 1) {
index++;
}
// next section
mp = strchr(cp, '|');
if (mp) mp++;
}
if (json) {
//snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s}", b_mqtt_data);
//ResponseAppend_P(PSTR("%s"),b_mqtt_data);
if (!nojson) {
ResponseAppend_P(PSTR("}"));
}
} else {
//WSContentSend_PD(PSTR("%s"),b_mqtt_data);
}
#ifdef USE_DOMOTICZ
if (json && !TasmotaGlobal.tele_period) {
char str[16];
DOUBLE2CHAR(sml_globs.meter_vars[0], 1, str);
DomoticzSensorPowerEnergy(sml_globs.meter_vars[1], str); // PowerUsage, EnergyToday
DOUBLE2CHAR(sml_globs.meter_vars[2], 1, str);
DomoticzSensor(DZ_VOLTAGE, str); // Voltage
DOUBLE2CHAR(sml_globs.meter_vars[3], 1, str);
DomoticzSensor(DZ_CURRENT, str); // Current
}
#endif // USE_DOMOTICZ
}
struct SML_COUNTER {
uint8_t sml_cnt_debounce;
uint8_t sml_cnt_old_state;
uint32_t sml_cnt_last_ts;
uint32_t sml_counter_ltime;
uint32_t sml_counter_lfalltime;
uint32_t sml_counter_pulsewidth;
uint16_t sml_debounce;
uint8_t sml_cnt_updated;
} sml_counters[MAX_COUNTERS];
uint8_t sml_counter_pinstate;
uint8_t sml_cnt_index[MAX_COUNTERS] = { 0, 1, 2, 3 };
void IRAM_ATTR SML_CounterIsr(void *arg) {
uint32_t index = *static_cast<uint8_t*>(arg);
uint32_t time = millis();
uint32_t debounce_time;
if (digitalRead(sml_globs.mp[sml_counters[index].sml_cnt_old_state].srcpin) == bitRead(sml_counter_pinstate, index)) {
return;
}
debounce_time = time - sml_counters[index].sml_counter_ltime;
if (debounce_time <= sml_counters[index].sml_debounce) return;
if bitRead(sml_counter_pinstate, index) {
// falling edge
RtcSettings.pulse_counter[index]++;
sml_counters[index].sml_counter_pulsewidth = time - sml_counters[index].sml_counter_lfalltime;
sml_counters[index].sml_counter_lfalltime = time;
sml_counters[index].sml_cnt_updated = 1;
}
sml_counters[index].sml_counter_ltime = time;
sml_counter_pinstate ^= (1 << index);
}
#ifndef METER_DEF_SIZE
#define METER_DEF_SIZE 3000
#endif
#ifdef SML_REPLACE_VARS
#ifndef SML_SRCBSIZE
#define SML_SRCBSIZE 256
#endif
uint32_t SML_getlinelen(char *lp) {
uint32_t cnt;
for (cnt = 0; cnt < SML_SRCBSIZE - 1; cnt++) {
if (lp[cnt] == SCRIPT_EOL) {
break;
}
}
return cnt;
}
uint32_t SML_getscriptsize(char *lp) {
uint32_t mlen = 0;
char dstbuf[SML_SRCBSIZE * 2];
while (1) {
Replace_Cmd_Vars(lp, 1, dstbuf, sizeof(dstbuf));
lp += SML_getlinelen(lp) + 1;
uint32_t slen = strlen(dstbuf);
//AddLog(LOG_LEVEL_INFO, PSTR("%d - %s"),slen,dstbuf);
mlen += slen + 1;
if (*lp == '#') break;
if (*lp == '>') break;
if (*lp == 0) break;
}
//AddLog(LOG_LEVEL_INFO, PSTR("len=%d"),mlen);
return mlen + 32;
}
#else
uint32_t SML_getscriptsize(char *lp) {
uint32_t mlen = 0;
for (uint32_t cnt = 0; cnt < METER_DEF_SIZE - 1; cnt++) {
if (lp[cnt] == '\n' && lp[cnt + 1] == '#') {
mlen = cnt + 3;
break;
}
}
//AddLog(LOG_LEVEL_INFO, PSTR("len=%d"),mlen);
return mlen;
}
#endif // SML_REPLACE_VARS
bool Gpio_used(uint8_t gpiopin) {
if ((gpiopin < nitems(TasmotaGlobal.gpio_pin)) && (TasmotaGlobal.gpio_pin[gpiopin] > 0)) {
return true;
}
return false;
}
#define SML_MINSB 64
char *SpecOptions(char *cp, uint32_t mnum) {
// special option
struct METER_DESC *mp = &meter_desc[mnum];
switch (*cp) {
case '1':
cp++;
#ifdef USE_SML_SPECOPT
if (*cp == ',') {
cp++;
mp->so_obis1 = strtol(cp, &cp, 16);
}
if (*cp == ',') {
cp++;
mp->so_fcode1 = strtol(cp, &cp, 16);
}
if (*cp == ',') {
cp++;
mp->so_bpos1 = strtol(cp, &cp, 10);
}
if (*cp == ',') {
cp++;
mp->so_fcode2 = strtol(cp, &cp, 16);
}
if (*cp == ',') {
cp++;
mp->so_bpos2 = strtol(cp, &cp, 10);
}
if (*cp == ',') {
cp++;
mp->so_obis2 = strtol(cp, &cp, 16);
}
#endif
break;
case '2':
cp += 2;
mp->so_flags = strtol(cp, &cp, 16);
break;
case '3':
cp += 2;
mp->sbsiz = strtol(cp, &cp, 10);
if (*cp == ',') {
cp++;
mp->sibsiz = strtol(cp, &cp, 10);
if (mp->sibsiz < SML_MINSB) {
mp->sibsiz = SML_MINSB;
}
}
if (*cp == ',') {
cp++;
sml_globs.logsize = strtol(cp, &cp, 10);
}
break;
case '4':
cp += 2;
#ifdef USE_SML_DECRYPT
meter_desc[mnum].use_crypt = true;
for (uint8_t cnt = 0; cnt < (SML_CRYPT_SIZE * 2); cnt += 2) {
mp->key[cnt / 2] = (sml_hexnibble(cp[cnt]) << 4) | sml_hexnibble(cp[cnt + 1]);
}
AddLog(LOG_LEVEL_INFO, PSTR("SML: crypto mode used for meter %d"), mnum + 1);
break;
#ifdef USE_SML_AUTHKEY
case '5':
cp += 2;
for (uint8_t cnt = 0; cnt < (SML_CRYPT_SIZE * 2); cnt += 2) {
mp->auth[cnt / 2] = (sml_hexnibble(cp[cnt]) << 4) | sml_hexnibble(cp[cnt + 1]);
}
break;
case '6':
cp += 2;
mp->tout_ms = strtol(cp, &cp, 10);
break;
#endif
#endif
}
return cp;
}
#ifdef USE_SML_DECRYPT
uint16_t serial_dispatch(uint8_t meter, uint8_t sel) {
struct METER_DESC *mp = &meter_desc[meter];
if (!sel) {
return mp->meter_ss->available();
}
uint8_t iob = mp->meter_ss->read();
return iob;
}
int SML_print(const char *format, ...) {
static char loc_buf[64];
char* temp = loc_buf;
int len;
va_list arg;
va_list copy;
va_start(arg, format);
va_copy(copy, arg);
len = vsnprintf(NULL, 0, format, arg);
va_end(copy);
if (len >= sizeof(loc_buf)) {
temp = (char*)malloc(len + 1);
if (temp == NULL) {
return 0;
}
}
vsnprintf(temp, len + 1, format, arg);
AddLog(LOG_LEVEL_DEBUG, PSTR("SML: %s"),temp);
va_end(arg);
if (len >= sizeof(loc_buf)) {
free(temp);
}
return len;
}
#endif // USE_SML_DECRYPT
void reset_sml_vars(uint16_t maxmeters) {
for (uint32_t meters = 0; meters < maxmeters; meters++) {
struct METER_DESC *mp = &meter_desc[meters];
mp->spos = 0;
mp->sbsiz = SML_BSIZ;
mp->sibsiz = TMSBSIZ;
if (mp->sbuff) {
free(mp->sbuff);
mp->sbuff = 0;
}
#ifdef USE_SML_SPECOPT
mp->so_obis1 = 0;
mp->so_obis2 = 0;
#endif
mp->so_flags = 0;
// addresses a bug in meter DWS74
#ifdef DWS74_BUG
mp->so_flags |= SO_DWS74_BUG;
#endif
#ifdef SML_OBIS_LINE
mp->so_flags |= SO_OBIS_LINE;
#endif
if (mp->txmem) {
free(mp->txmem);
mp->txmem = 0;
}
mp->txmem = 0;
mp->trxpin = -1;
if (mp->meter_ss) {
delete mp->meter_ss;
mp->meter_ss = NULL;
}
mp->lastms = millis();
mp->tout_ms = SML_STIMEOUT;
#ifdef USE_SML_DECRYPT
if (mp->use_crypt) {
if (mp->hp) {
delete mp->hp;
mp->hp = NULL;
}
}
mp->use_crypt = 0;
#ifdef USE_SML_AUTHKEY
memset(mp->auth, 0, SML_CRYPT_SIZE);
#endif
#endif // USE_SML_DECRYPT
}
}
void SML_Init(void) {
sml_globs.ready = false;
if (!bitRead(Settings->rule_enabled, 0)) {
return;
}
sml_globs.mp = meter_desc;
uint8_t meter_script = Run_Scripter(">M", -2, 0);
if (meter_script != 99) {
AddLog(LOG_LEVEL_INFO, PSTR("no meter section found!"));
return;
}
char *lp = glob_script_mem.section_ptr;
uint8_t new_meters_used;
// use script definition
if (sml_globs.script_meter) {
// restart condition
free(sml_globs.script_meter);
if (sml_globs.meter_vars) {
free(sml_globs.meter_vars);
sml_globs.meter_vars = 0;
}
if (sml_globs.dvalid) {
free(sml_globs.dvalid);
sml_globs.dvalid = 0;
}
#ifdef USE_SML_MEDIAN_FILTER
if (sml_globs.sml_mf) {
free(sml_globs.sml_mf);
sml_globs.sml_mf = 0;
}
#endif
reset_sml_vars(sml_globs.meters_used);
}
if (*lp == '>' && *(lp + 1) == 'M') {
lp += 2;
sml_globs.meters_used = strtol(lp, &lp, 10);
} else {
return;
}
sml_globs.maxvars = 0;
reset_sml_vars(sml_globs.meters_used);
sml_globs.sml_desc_cnt = 0;
sml_globs.script_meter = 0;
uint8_t *tp = 0;
uint16_t index = 0;
uint8_t section = 0;
int8_t srcpin = 0;
uint32_t mlen;
uint16_t memory = 0;
sml_globs.sml_send_blocks = 0;
lp = glob_script_mem.section_ptr;
while (lp) {
if (!section) {
if (*lp == '>' && *(lp + 1) == 'M') {
lp += 2;
section = 1;
mlen = SML_getscriptsize(lp);
if (mlen == 0) return; // missing end #
sml_globs.script_meter = (uint8_t*)calloc(mlen, 1);
memory += mlen;
if (!sml_globs.script_meter) {
goto dddef_exit;
}
tp = sml_globs.script_meter;
goto next_line;
}
}
else {
if (!*lp || *lp == '#' || *lp == '>') {
if (*(tp - 1) == '|') *(tp - 1) = 0;
break;
}
if (*lp == '+') {
// add descriptor +1,1,c,0,10,H20
//toLogEOL(">>",lp);
lp++;
index = *lp & 7;
lp += 2;
if (index < 1 || index > sml_globs.meters_used) {
AddLog(LOG_LEVEL_INFO, PSTR("illegal meter number!"));
goto next_line;
}
index--;
srcpin = strtol(lp, &lp, 10);
if (Gpio_used(abs(srcpin))) {
AddLog(LOG_LEVEL_INFO, PSTR("SML: Error: Duplicate GPIO %d defined. Not usable for RX in meter number %d"), abs(srcpin), index + 1);
dddef_exit:
if (sml_globs.script_meter) free(sml_globs.script_meter);
sml_globs.script_meter = 0;
return;
}
meter_desc[index].srcpin = srcpin;
if (*lp != ',') goto next_line;
lp++;
meter_desc[index].type = *lp;
lp++;
if (*lp != ',') {
switch (*lp) {
case 'N':
lp++;
meter_desc[index].sopt = 0x10 | (*lp & 3);
lp++;
break;
case 'E':
lp++;
meter_desc[index].sopt = 0x20 | (*lp & 3);
lp++;
break;
case 'O':
lp++;
meter_desc[index].sopt = 0x30 | (*lp & 3);
lp++;
break;
default:
meter_desc[index].sopt = *lp&7;
lp++;
}
} else {
meter_desc[index].sopt = 0;
}
lp++;
meter_desc[index].flag = strtol(lp, &lp, 10);
if (*lp != ',') goto next_line;
lp++;
meter_desc[index].params = strtol(lp, &lp, 10);
if (*lp != ',') goto next_line;
lp++;
meter_desc[index].prefix[7] = 0;
for (uint32_t cnt = 0; cnt < 8; cnt++) {
if (*lp == SCRIPT_EOL || *lp == ',') {
meter_desc[index].prefix[cnt] = 0;
break;
}
meter_desc[index].prefix[cnt] = *lp++;
}
if (*lp == ',') {
lp++;
// get TRX pin
meter_desc[index].trxpin = strtol(lp, &lp, 10);
if (Gpio_used(meter_desc[index].trxpin)) {
AddLog(LOG_LEVEL_INFO, PSTR("SML: Error: Duplicate GPIO %d defined. Not usable for TX in meter number %d"), meter_desc[index].trxpin, index + 1);
goto dddef_exit;
}
// optional transmit enable pin
if (*lp == '(') {
lp++;
if (*lp == 'i') {
lp++;
meter_desc[index].trx_en.trxenpol = 1;
} else {
meter_desc[index].trx_en.trxenpol = 0;
}
meter_desc[index].trx_en.trxenpin = strtol(lp, &lp, 10);
if (*lp != ')') {
goto dddef_exit;
}
lp++;
if (Gpio_used(meter_desc[index].trx_en.trxenpin)) {
AddLog(LOG_LEVEL_INFO, PSTR("SML: Error: Duplicate GPIO %d defined. Not usable for TX enable in meter number %d"), meter_desc[index].trx_en.trxenpin, index + 1);
goto dddef_exit;
}
meter_desc[index].trx_en.trxen = 1;
pinMode(meter_desc[index].trx_en.trxenpin, OUTPUT);
digitalWrite(meter_desc[index].trx_en.trxenpin, meter_desc[index].trx_en.trxenpol);
} else {
meter_desc[index].trx_en.trxen = 0;
}
if (*lp != ',') goto next_line;
lp++;
meter_desc[index].tsecs = strtol(lp, &lp, 10);
if (*lp == ',') {
lp++;
// look ahead
uint16_t txlen = 0;
uint16_t tx_entries = 1;
char *txp = lp;
while (*txp) {
if (*txp == ',') tx_entries++;
if (*txp == SCRIPT_EOL) {
if (tx_entries > 1) {
if (*(txp - 1) != ',' ) {
break;
}
// line ends with ,
} else {
// single entry
break;
}
}
txp++;
txlen++;
}
if (txlen) {
meter_desc[index].txmem = (char*)calloc(txlen + 2, 1);
memory += txlen + 2;
if (meter_desc[index].txmem) {
// now copy send blocks
char *txp = lp;
uint16_t tind = 0;
for (uint32_t cnt = 0; cnt < txlen; cnt++) {
if (*txp == SCRIPT_EOL) {
txp++;
} else {
meter_desc[index].txmem[tind] = *txp++;
tind++;
}
}
}
//AddLog(LOG_LEVEL_INFO, PSTR(">>> %s - %d"), meter_desc[index].txmem, txlen);
meter_desc[index].index = 0;
meter_desc[index].max_index = tx_entries;
sml_globs.sml_send_blocks++;
lp += txlen;
}
}
}
if (*lp == SCRIPT_EOL) lp--;
goto next_line;
}
char *lp1;
#ifdef SML_REPLACE_VARS
char dstbuf[SML_SRCBSIZE*2];
Replace_Cmd_Vars(lp, 1, dstbuf, sizeof(dstbuf));
lp += SML_getlinelen(lp);
lp1 = dstbuf;
#else
lp1 = lp;
lp += SML_getlinelen(lp);
#endif // SML_REPLACE_VARS
//AddLog(LOG_LEVEL_INFO, PSTR("%s"),dstbuf);
if (*lp1 == '-' || isdigit(*lp1)) {
//toLogEOL(">>",lp);
// add meters line -1,1-0:1.8.0*255(@10000,H2OIN,cbm,COUNTER,4|
if (*lp1 == '-') lp1++;
uint8_t mnum = strtol(lp1, 0, 10);
if (mnum < 1 || mnum > sml_globs.meters_used) {
AddLog(LOG_LEVEL_INFO, PSTR("illegal meter number!"));
goto next_line;
}
// 1,=h—————————————
if (!strncmp(lp1 + 1, ",=h", 3) || !strncmp(lp1 + 1, ",=so", 4)) {
if (!strncmp(lp1 + 1, ",=so", 4)) {
SpecOptions(lp1 + 5, mnum - 1);
}
} else {
sml_globs.maxvars++;
}
while (1) {
if (*lp1 == 0) {
*tp++ = '|';
goto next_line;
}
*tp++ = *lp1++;
index++;
if (index >= METER_DEF_SIZE) break;
}
}
}
next_line:
if (*lp == SCRIPT_EOL) {
lp++;
} else {
lp = strchr(lp, SCRIPT_EOL);
if (!lp) break;
lp++;
}
}
*tp = 0;
sml_globs.meter_p = sml_globs.script_meter;
// set serial buffers
for (uint32_t meters = 0; meters < sml_globs.meters_used; meters++ ) {
struct METER_DESC *mp = &meter_desc[meters];
if (mp->sbsiz) {
mp->sbuff = (uint8_t*)calloc(mp->sbsiz, 1);
memory += mp->sbsiz;
}
}
// initialize hardware
typedef void (*function)();
uint8_t cindex = 0;
// preloud counters
for (uint8_t i = 0; i < MAX_COUNTERS; i++) {
RtcSettings.pulse_counter[i] = Settings->pulse_counter[i];
sml_counters[i].sml_cnt_last_ts = millis();
}
#ifdef ESP32
uint32_t uart_index = SOC_UART_NUM - 1;
#endif
sml_counter_pinstate = 0;
for (uint8_t meters = 0; meters < sml_globs.meters_used; meters++) {
if (sml_globs.mp[meters].type == 'c') {
if (sml_globs.mp[meters].flag & 2) {
} else {
// counters, set to input with pullup
if (sml_globs.mp[meters].flag & 1) {
pinMode(sml_globs.mp[meters].srcpin, INPUT_PULLUP);
} else {
pinMode(sml_globs.mp[meters].srcpin, INPUT);
}
// check for irq mode
if (sml_globs.mp[meters].params <= 0) {
// init irq mode
sml_counters[cindex].sml_cnt_old_state = meters;
sml_counters[cindex].sml_debounce = -sml_globs.mp[meters].params;
attachInterruptArg(sml_globs.mp[meters].srcpin, SML_CounterIsr, &sml_cnt_index[cindex], CHANGE);
if (digitalRead(sml_globs.mp[meters].srcpin) > 0) {
sml_counter_pinstate |= (1 << cindex);
}
sml_counters[cindex].sml_counter_ltime = millis();
}
RtcSettings.pulse_counter[cindex] = Settings->pulse_counter[cindex];
InjektCounterValue(meters, RtcSettings.pulse_counter[cindex], 0.0);
cindex++;
}
} else {
// serial input, init
#ifdef ESP8266
#ifdef SPECIAL_SS
char type = sml_globs.mp[meters].type;
if (type == 'm' || type == 'M' || type == 'k' || type == 'p' || type == 'R' || type == 'v') {
meter_desc[meters].meter_ss = new TasmotaSerial(sml_globs.mp[meters].srcpin,sml_globs.mp[meters].trxpin, 1, 0, meter_desc[meters].sibsiz);
} else {
meter_desc[meters].meter_ss = new TasmotaSerial(sml_globs.mp[meters].srcpin,sml_globs.mp[meters].trxpin, 1, 1, meter_desc[meters].sibsiz);
}
#else
meter_desc[meters].meter_ss = new TasmotaSerial(sml_globs.mp[meters].srcpin,sml_globs.mp[meters].trxpin, 1, 0, meter_desc[meters].sibsiz);
#endif // SPECIAL_SS
#endif // ESP8266
#ifdef ESP32
// use hardware serial
#ifdef USE_ESP32_SW_SERIAL
meter_desc[meters].meter_ss = new SML_ESP32_SERIAL(uart_index);
if (sml_globs.mp[meters].srcpin >= 0) {
if (uart_index == 0) { ClaimSerial(); }
uart_index--;
if (uart_index < 0) uart_index = 0;
}
#else
meter_desc[meters].meter_ss = new HardwareSerial(uart_index);
if (uart_index == 0) { ClaimSerial(); }
uart_index--;
if (uart_index < 0) uart_index = 0;
meter_desc[meters].meter_ss->setRxBufferSize(meter_desc[meters].sibsiz);
#endif // USE_ESP32_SW_SERIAL
#endif // ESP32
SerialConfig smode = SERIAL_8N1;
if (sml_globs.mp[meters].sopt & 0xf0) {
// new serial config
switch (sml_globs.mp[meters].sopt >> 4) {
case 1:
if ((sml_globs.mp[meters].sopt & 1) == 1) smode = SERIAL_8N1;
else smode = SERIAL_8N2;
break;
case 2:
if ((sml_globs.mp[meters].sopt & 1) == 1) smode = SERIAL_8E1;
else smode = SERIAL_8E2;
break;
case 3:
if ((sml_globs.mp[meters].sopt & 1) == 1) smode = SERIAL_8O1;
else smode = SERIAL_8O2;
break;
}
} else {
// deprecated serial config
if (sml_globs.mp[meters].sopt == 2) {
smode = SERIAL_8N2;
}
if (sml_globs.mp[meters].type=='M') {
smode = SERIAL_8E1;
if (sml_globs.mp[meters].sopt == 2) {
smode = SERIAL_8E2;
}
}
}
#ifdef ESP8266
if (meter_desc[meters].meter_ss->begin(sml_globs.mp[meters].params)) {
meter_desc[meters].meter_ss->flush();
}
if (meter_desc[meters].meter_ss->hardwareSerial()) {
Serial.begin(sml_globs.mp[meters].params, smode);
ClaimSerial();
//Serial.setRxBufferSize(512);
}
#endif // ESP8266
#ifdef ESP32
meter_desc[meters].meter_ss->begin(sml_globs.mp[meters].params, smode, sml_globs.mp[meters].srcpin, sml_globs.mp[meters].trxpin);
#ifdef USE_ESP32_SW_SERIAL
meter_desc[meters].meter_ss->setRxBufferSize(meter_desc[meters].sibsiz);
#endif
#endif // ESP32
}
}
sml_globs.meter_vars = (double*)calloc(sml_globs.maxvars, sizeof(double));
sml_globs.dvalid = (uint8_t*)calloc(sml_globs.maxvars, sizeof(uint8_t));
#ifdef USE_SML_MEDIAN_FILTER
sml_globs.sml_mf = (struct SML_MEDIAN_FILTER*)calloc(sml_globs.maxvars, sizeof(struct SML_MEDIAN_FILTER));
#endif
if (!sml_globs.maxvars || !sml_globs.meter_vars || !sml_globs.dvalid || !sml_globs.sml_mf) {
AddLog(LOG_LEVEL_INFO, PSTR("sml memory error!"));
return;
}
memory += sizeof(sml_globs) + sizeof(meter_desc) + sml_globs.maxvars * (sizeof(double) + sizeof(uint8_t) + sizeof(struct SML_MEDIAN_FILTER));
AddLog(LOG_LEVEL_INFO, PSTR("meters: %d , decode lines: %d, memory used: %d bytes"), sml_globs.meters_used, sml_globs.maxvars, memory);
// speed optimize shift flag
for (uint32_t meters = 0; meters < sml_globs.meters_used; meters++ ) {
struct METER_DESC *mp = &meter_desc[meters];
char type = mp->type;
if (!(mp->so_flags & SO_OBIS_LINE)) {
mp->shift_mode = (type != 'e' && type != 'k' && type != 'm' && type != 'M' && type != 'p' && type != 'R' && type != 'v');
} else {
mp->shift_mode = (type != 'o' && type != 'e' && type != 'k' && type != 'm' && type != 'M' && type != 'p' && type != 'R' && type != 'v');
}
#ifdef USE_SML_DECRYPT
if (mp->use_crypt) {
#ifdef USE_SML_AUTHKEY
mp->hp = new Han_Parser(serial_dispatch, meters, mp->key, mp->auth);
#else
mp->hp = new Han_Parser(serial_dispatch, meters, mp->key, nullptr);
#endif
}
#endif
}
sml_globs.ready = true;
}
#ifdef USE_SML_SCRIPT_CMD
uint32_t SML_SetBaud(uint32_t meter, uint32_t br) {
if (sml_globs.ready == false) return 0;
if (meter < 1 || meter > sml_globs.meters_used) return 0;
meter--;
if (!meter_desc[meter].meter_ss) return 0;
#ifdef ESP8266
if (meter_desc[meter].meter_ss->begin(br)) {
meter_desc[meter].meter_ss->flush();
}
if (meter_desc[meter].meter_ss->hardwareSerial()) {
if (sml_globs.mp[meter].type=='M') {
Serial.begin(br, SERIAL_8E1);
}
}
#endif // ESP8266
#ifdef ESP32
meter_desc[meter].meter_ss->flush();
meter_desc[meter].meter_ss->updateBaudRate(br);
/*
if (sml_globs.mp[meter].type=='M') {
meter_desc.meter_ss[meter]->begin(br,SERIAL_8E1,sml_globs.mp[meter].srcpin,sml_globs.mp[meter].trxpin);
} else {
meter_desc.meter_ss[meter]->begin(br,SERIAL_8N1,sml_globs.mp[meter].srcpin,sml_globs.mp[meter].trxpin);
}*/
#endif // ESP32
return 1;
}
uint32_t sml_status(uint32_t meter) {
if (sml_globs.ready == false) return 0;
if (meter < 1 || meter > sml_globs.meters_used) return 0;
meter--;
#if defined(ED300L) || defined(AS2020) || defined(DTZ541) || defined(USE_SML_SPECOPT)
return sml_globs.sml_status[meter];
#else
return 0;
#endif
}
uint32_t SML_Write(int32_t meter, char *hstr) {
if (sml_globs.ready == false) return 0;
int8_t flag = meter;
meter = abs(meter);
if (meter < 1 || meter > sml_globs.meters_used) return 0;
meter--;
if (!meter_desc[meter].meter_ss) return 0;
if (flag > 0) {
SML_Send_Seq(meter, hstr);
} else {
// 9600:8E1, only hardware serial
uint32_t baud = strtol(hstr, &hstr, 10);
hstr++;
// currently only 8 bits and ignore stopbits
hstr++;
SerialConfig smode;
switch (*hstr) {
case 'N':
smode = SERIAL_8N1;
break;
case 'E':
smode = SERIAL_8E1;
break;
case 'O':
smode = SERIAL_8O1;
break;
}
#ifdef ESP8266
Serial.begin(baud, smode);
#else
meter_desc[meter].meter_ss->begin(baud, smode, sml_globs.mp[meter].srcpin, sml_globs.mp[meter].trxpin);
#endif
}
return 1;
}
uint32_t SML_Read(int32_t meter, char *str, uint32_t slen) {
if (sml_globs.ready == false) return 0;
uint8_t hflg = 0;
if (meter < 0) {
meter = abs(meter);
hflg = 1;
}
if (meter < 1 || meter > sml_globs.meters_used) return 0;
meter--;
if (!meter_desc[meter].meter_ss) return 0;
struct METER_DESC *mp = &meter_desc[meter];
if (!mp->spos) {
return 0;
}
mp->sbuff[mp->spos] = 0;
if (!hflg) {
strlcpy(str, (char*)&mp->sbuff[0], slen);
} else {
uint32_t index = 0;
for (uint32_t cnt = 0; cnt < mp->spos; cnt++) {
sprintf(str,"%02x", mp->sbuff[cnt]);
str += 2;
index += 2;
if (index >= slen - 2) break;
}
}
mp->spos = 0;
return 1;
}
uint32_t sml_getv(uint32_t sel) {
if (sml_globs.ready == false) return 0;
if (!sel) {
for (uint8_t cnt = 0; cnt < sml_globs.maxvars; cnt++) {
sml_globs.dvalid[cnt] = 0;
}
sel = 0;
} else {
if (sel < 1 || sel > sml_globs.maxvars) { sel = 1;}
sel = sml_globs.dvalid[sel - 1];
}
return sel;
}
double SML_GetVal(uint32_t index) {
if (sml_globs.ready == false) return 0;
if (index < 1 || index > sml_globs.maxvars) { index = 1;}
return sml_globs.meter_vars[index - 1];
}
char *SML_GetSVal(uint32_t index) {
if (sml_globs.ready == false) return 0;
if (index < 1 || index > sml_globs.meters_used) { index = 1;}
return (char*)meter_desc[index - 1].meter_id;
}
int32_t SML_Set_WStr(uint32_t meter, char *hstr) {
if (sml_globs.ready == false) return 0;
if (meter < 1 || meter > sml_globs.meters_used) return -1;
meter--;
if (!meter_desc[meter].meter_ss) return -2;
meter_desc[meter].script_str = hstr;
return 0;
}
#endif // USE_SML_SCRIPT_CMD
void SetDBGLed(uint8_t srcpin, uint8_t ledpin) {
pinMode(ledpin, OUTPUT);
if (digitalRead(srcpin)) {
digitalWrite(ledpin,LOW);
} else {
digitalWrite(ledpin,HIGH);
}
}
// force channel math on counters
void SML_Counter_Poll_1s(void) {
for (uint32_t meter = 0; meter < sml_globs.meters_used; meter++) {
if (sml_globs.mp[meter].type == 'c') {
SML_Decode(meter);
}
}
}
#define CNT_PULSE_TIMEOUT 5000
// fast counter polling
void SML_Counter_Poll(void) {
uint16_t meters, cindex = 0;
uint32_t ctime = millis();
for (meters = 0; meters < sml_globs.meters_used; meters++) {
if (sml_globs.mp[meters].type == 'c') {
// poll for counters and debouce
if (sml_globs.mp[meters].params > 0) {
if (ctime - sml_counters[cindex].sml_cnt_last_ts > sml_globs.mp[meters].params) {
sml_counters[cindex].sml_cnt_last_ts = ctime;
if (sml_globs.mp[meters].flag & 2) {
// analog mode, get next value
} else {
// poll digital input
uint8_t state;
sml_counters[cindex].sml_cnt_debounce <<= 1;
sml_counters[cindex].sml_cnt_debounce |= (digitalRead(sml_globs.mp[meters].srcpin) & 1) | 0x80;
if (sml_counters[cindex].sml_cnt_debounce == 0xc0) {
// is 1
state = 1;
} else {
// is 0, means switch down
state = 0;
}
if (sml_counters[cindex].sml_cnt_old_state != state) {
// state has changed
sml_counters[cindex].sml_cnt_old_state = state;
if (state == 0) {
// inc counter
RtcSettings.pulse_counter[cindex]++;
sml_counters[cindex].sml_counter_pulsewidth = ctime - sml_counters[cindex].sml_counter_lfalltime;
sml_counters[cindex].sml_counter_lfalltime = ctime;
InjektCounterValue(meters, RtcSettings.pulse_counter[cindex], 60000.0 / (float)sml_counters[cindex].sml_counter_pulsewidth);
}
}
}
}
#ifdef DEBUG_CNT_LED1
if (cindex == 0) SetDBGLed(sml_globs.mp[meters].srcpin, DEBUG_CNT_LED1);
#endif
#ifdef DEBUG_CNT_LED2
if (cindex == 1) SetDBGLed(sml_globs.mp[meters].srcpin, DEBUG_CNT_LED2);
#endif
} else {
if (ctime - sml_counters[cindex].sml_cnt_last_ts > 10) {
sml_counters[cindex].sml_cnt_last_ts = ctime;
#ifdef DEBUG_CNT_LED1
if (cindex == 0) SetDBGLed(sml_globs.mp[meters].srcpin, DEBUG_CNT_LED1);
#endif
#ifdef DEBUG_CNT_LED2
if (cindex == 1) SetDBGLed(sml_globs.mp[meters].srcpin, DEBUG_CNT_LED2);
#endif
}
if (sml_counters[cindex].sml_cnt_updated) {
InjektCounterValue(meters, RtcSettings.pulse_counter[cindex], 60000.0 / (float)sml_counters[cindex].sml_counter_pulsewidth);
sml_counters[cindex].sml_cnt_updated = 0;
}
// check timeout
uint32_t time = millis();
if ((time - sml_counters[cindex].sml_counter_lfalltime) > CNT_PULSE_TIMEOUT) {
InjektCounterValue(meters, RtcSettings.pulse_counter[cindex], 0);
sml_counters[cindex].sml_counter_lfalltime = time;
}
}
cindex++;
}
}
}
#ifdef USE_SCRIPT
char *SML_Get_Sequence(char *cp,uint32_t index) {
if (!index) return cp;
uint32_t cindex = 0;
while (cp) {
cp = strchr(cp, ',');
if (cp) {
cp++;
cindex++;
if (cindex == index) {
return cp;
}
}
}
return cp;
}
void SML_Check_Send(void) {
sml_globs.sml_100ms_cnt++;
char *cp;
for (uint32_t cnt = sml_globs.sml_desc_cnt; cnt < sml_globs.meters_used; cnt++) {
if (meter_desc[cnt].trxpin >= 0 && meter_desc[cnt].txmem) {
//AddLog(LOG_LEVEL_INFO, PSTR("100 ms>> %d - %s - %d"),sml_globs.sml_desc_cnt,meter_desc[cnt].txmem,meter_desc[cnt].tsecs);
if ((sml_globs.sml_100ms_cnt >= meter_desc[cnt].tsecs)) {
sml_globs.sml_100ms_cnt = 0;
// check for scriptsync extra output
if (meter_desc[cnt].script_str) {
cp = meter_desc[cnt].script_str;
meter_desc[cnt].script_str = 0;
} else {
//AddLog(LOG_LEVEL_INFO, PSTR("100 ms>> 2"),cp);
if (meter_desc[cnt].max_index > 1) {
meter_desc[cnt].index++;
if (meter_desc[cnt].index >= meter_desc[cnt].max_index) {
meter_desc[cnt].index = 0;
sml_globs.sml_desc_cnt++;
}
cp = SML_Get_Sequence(meter_desc[cnt].txmem, meter_desc[cnt].index);
//SML_Send_Seq(cnt,cp);
} else {
cp = meter_desc[cnt].txmem;
//SML_Send_Seq(cnt,cp);
sml_globs.sml_desc_cnt++;
}
}
//AddLog(LOG_LEVEL_INFO, PSTR(">> %s"),cp);
SML_Send_Seq(cnt,cp);
if (sml_globs.sml_desc_cnt >= sml_globs.meters_used) {
sml_globs.sml_desc_cnt = 0;
}
break;
}
} else {
sml_globs.sml_desc_cnt++;
}
if (sml_globs.sml_desc_cnt >= sml_globs.meters_used) {
sml_globs.sml_desc_cnt = 0;
}
}
}
void sml_hex_asci(uint32_t mindex, char *tpowstr) {
char *cp = meter_desc[mindex].meter_id;
uint16_t slen = strlen(cp);
slen &= 0xfffe;
uint16_t cnt;
*tpowstr++ = '"';
for (cnt = 0; cnt < slen; cnt += 2) {
uint8_t iob = (sml_hexnibble(cp[cnt]) << 4) | sml_hexnibble(cp[cnt + 1]);
*tpowstr++ = iob;
}
*tpowstr++ = '"';
*tpowstr = 0;
}
uint8_t sml_hexnibble(char chr) {
uint8_t rVal = 0;
if (isdigit(chr)) {
rVal = chr - '0';
} else {
if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A';
if (chr >= 'a' && chr <= 'f') rVal = chr + 10 - 'a';
}
return rVal;
}
// send sequence every N Seconds
void SML_Send_Seq(uint32_t meter,char *seq) {
uint8_t sbuff[48];
uint8_t *ucp = sbuff, slen = 0;
char *cp = seq;
uint8_t rflg = 0;
if (*cp == 'r') {
rflg = 1;
cp++;
}
while (*cp) {
if (!*cp || !*(cp+1)) break;
if (*cp == ',') break;
uint8_t iob = (sml_hexnibble(*cp) << 4) | sml_hexnibble(*(cp + 1));
cp += 2;
*ucp++ = iob;
slen++;
if (slen >= sizeof(sbuff)-6) break; // leave space for checksum
}
if (meter_desc[meter].type == 'm' || meter_desc[meter].type == 'M' || meter_desc[meter].type == 'k') {
if (meter_desc[meter].type == 'k') {
// kamstrup, append crc, cr
*ucp++ = 0;
*ucp++ = 0;
slen += 2;
uint16_t crc = KS_calculateCRC(sbuff, slen);
ucp -= 2;
*ucp++ = highByte(crc);
*ucp++ = lowByte(crc);
// now check for escapes
uint8_t ksbuff[24];
ucp = ksbuff;
*ucp++ = 0x80;
uint8_t klen = 1;
for (uint16_t cnt = 0; cnt < slen; cnt++) {
uint8_t iob = sbuff[cnt];
if ((iob == 0x80) || (iob == 0x40) || (iob == 0x0d) || (iob == 0x06) || (iob == 0x1b)) {
*ucp++ = 0x1b;
*ucp++ = iob ^= 0xff;
klen += 2;
} else {
*ucp++ = iob;
klen++;
}
}
*ucp++ = 0xd;
slen = klen + 1;
memcpy(sbuff, ksbuff, slen);
} else {
if (!rflg) {
*ucp++ = 0;
*ucp++ = 2;
slen += 2;
}
// append crc
uint16_t crc = MBUS_calculateCRC(sbuff, slen, 0xFFFF);
*ucp++ = lowByte(crc);
*ucp++ = highByte(crc);
slen += 2;
}
}
if (meter_desc[meter].type == 'o') {
for (uint32_t cnt = 0; cnt < slen; cnt++) {
sbuff[cnt] |= (CalcEvenParity(sbuff[cnt]) << 7);
}
}
if (meter_desc[meter].type == 'p') {
*ucp++ = 0xc0;
*ucp++ = 0xa8;
*ucp++ = 1;
*ucp++ = 1;
*ucp++ = 0;
*ucp++ = SML_PzemCrc(sbuff, 6);
slen += 6;
}
if (meter_desc[meter].trx_en.trxen) {
digitalWrite(meter_desc[meter].trx_en.trxenpin, meter_desc[meter].trx_en.trxenpol ^ 1);
}
meter_desc[meter].meter_ss->flush();
meter_desc[meter].meter_ss->write(sbuff, slen);
if (meter_desc[meter].trx_en.trxen) {
// must wait for all data sent
meter_desc[meter].meter_ss->flush();
digitalWrite(meter_desc[meter].trx_en.trxenpin, meter_desc[meter].trx_en.trxenpol);
}
if (sml_globs.dump2log) {
#ifdef SML_DUMP_OUT_ALL
Hexdump(sbuff, slen);
#else
uint8_t type = sml_globs.mp[(sml_globs.dump2log&7) - 1].type;
if (type == 'm' || type == 'M' || type == 'k') {
Hexdump(sbuff, slen);
}
#endif
}
#ifdef MODBUS_DEBUG
uint8_t type = meter_desc[meter].type;
if (!sml_globs.dump2log && (type == 'm' || type == 'M' || type == 'k')) {
AddLog(LOG_LEVEL_INFO, PSTR("transmit index >> %d"),sml_globs.mp[meter].index);
Hexdump(sbuff, slen);
}
#endif
}
#endif // USE_SCRIPT
uint16_t MBUS_calculateCRC(uint8_t *frame, uint8_t num, uint16_t start) {
uint16_t crc, flag;
//crc = 0xFFFF;
crc = start;
for (uint32_t i = 0; i < num; i++) {
crc ^= frame[i];
for (uint32_t j = 8; j; j--) {
if ((crc & 0x0001) != 0) { // If the LSB is set
crc >>= 1; // Shift right and XOR 0xA001
crc ^= 0xA001;
} else { // Else LSB is not set
crc >>= 1; // Just shift right
}
}
}
return crc;
}
uint16_t KS_calculateCRC(const uint8_t *frame, uint8_t num) {
uint32_t crc = 0;
for (uint32_t i = 0; i < num; i++) {
uint8_t mask = 0x80;
uint8_t iob = frame[i];
while (mask) {
crc <<= 1;
if (iob & mask) {
crc |= 1;
}
mask >>= 1;
if (crc & 0x10000) {
crc &= 0xffff;
crc ^= 0x1021;
}
}
}
return crc;
}
uint8_t SML_PzemCrc(uint8_t *data, uint8_t len) {
uint16_t crc = 0;
for (uint32_t i = 0; i < len; i++) crc += *data++;
return (uint8_t)(crc & 0xFF);
}
// for odd parity init with 1
uint8_t CalcEvenParity(uint8_t data) {
uint8_t parity=0;
while(data) {
parity^=(data &1);
data>>=1;
}
return parity;
}
// dump to log shows serial data on console
// has to be off for normal use
// in console sensor53 d1, d2, d3 ... or d0 for normal use
// set counter => sensor53 c1 xxxx
// restart driver => sensor53 r
// meter number for monitoring serial activity => sensor53 m1, m2, m3 ... or m0 for all (default)
// LED-GPIO for monitoring serial activity => sensor53 l2, l13, l15 ... or l255 for turn off (default)
bool XSNS_53_cmd(void) {
bool serviced = true;
if (XdrvMailbox.data_len > 0) {
char *cp = XdrvMailbox.data;
if (*cp == 'd') {
// set dump mode
if (sml_globs.ready) {
cp++;
uint8_t index = atoi(cp);
if ((index & 7) > sml_globs.meters_used) index = 1;
if (index > 0 && sml_globs.mp[(index & 7) - 1].type == 'c') {
index = 0;
}
if (sml_globs.log_data) {
free(sml_globs.log_data);
sml_globs.log_data = 0;
}
if (index > 0) {
sml_globs.log_data = (char*)calloc(sml_globs.logsize, sizeof(char));
}
sml_globs.dump2log = index;
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"dump: %d\"}}"), sml_globs.dump2log);
}
} else if (*cp == 'c') {
// set counter
cp++;
uint8_t index = *cp&7;
if (index < 1 || index > MAX_COUNTERS) index = 1;
cp++;
while (*cp == ' ') cp++;
if (isdigit(*cp)) {
uint32_t cval = atoi(cp);
while (isdigit(*cp)) cp++;
RtcSettings.pulse_counter[index - 1] = cval;
uint8_t cindex = 0;
for (uint8_t meters = 0; meters < sml_globs.meters_used; meters++) {
if (sml_globs.mp[meters].type == 'c') {
InjektCounterValue(meters, RtcSettings.pulse_counter[cindex], 0.0);
cindex++;
}
}
}
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"counter%d: %d\"}}"), index, RtcSettings.pulse_counter[index - 1]);
} else if (*cp == 'r') {
// restart
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"restart\"}}"));
SML_CounterSaveState();
SML_Init();
} else if (*cp == 'm') {
// meter number for serial activity
cp++;
if (!isdigit(*cp)) {
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_meter_num: %d\"}}"), sml_globs.ser_act_meter_num);
} else {
sml_globs.ser_act_meter_num = atoi(cp);
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_meter_num: %d\"}}"), sml_globs.ser_act_meter_num);
}
} else if (*cp == 'l') {
// serial activity LED-GPIO
cp++;
if (!isdigit(*cp)) {
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_LED_pin: %d\"}}"), sml_globs.ser_act_LED_pin);
} else {
sml_globs.ser_act_LED_pin = atoi(cp);
if (Gpio_used(sml_globs.ser_act_LED_pin)) {
AddLog(LOG_LEVEL_INFO, PSTR("SML: Error: Duplicate GPIO %d defined. Not usable for LED."), sml_globs.ser_act_LED_pin);
sml_globs.ser_act_LED_pin = 255;
}
if (sml_globs.ser_act_LED_pin != 255) {
pinMode(sml_globs.ser_act_LED_pin, OUTPUT);
}
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"sml_globs.ser_act_LED_pin: %d\"}}"), sml_globs.ser_act_LED_pin);
}
} else {
serviced = false;
}
}
return serviced;
}
void InjektCounterValue(uint8_t meter, uint32_t counter, float rate) {
snprintf((char*)&meter_desc[meter].sbuff[0], meter_desc[meter].sbsiz, "1-0:1.8.0*255(%d)", counter);
SML_Decode(meter);
char freq[16];
freq[0] = 0;
if (rate) {
DOUBLE2CHAR(rate, 4, freq);
}
snprintf((char*)&meter_desc[meter].sbuff[0], meter_desc[meter].sbsiz, "1-0:1.7.0*255(%s)", freq);
SML_Decode(meter);
}
void SML_CounterSaveState(void) {
for (byte i = 0; i < MAX_COUNTERS; i++) {
Settings->pulse_counter[i] = RtcSettings.pulse_counter[i];
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns53(uint32_t function) {
bool result = false;
switch (function) {
case FUNC_INIT:
SML_Init();
break;
case FUNC_LOOP:
if (sml_globs.ready) {
SML_Counter_Poll();
if (sml_globs.dump2log) {
dump2log();
} else {
SML_Poll();
}
}
break;
case FUNC_EVERY_100_MSECOND:
if (bitRead(Settings->rule_enabled, 0)) {
if (sml_globs.ready) {
SML_Check_Send();
}
}
break;
case FUNC_EVERY_SECOND:
if (bitRead(Settings->rule_enabled, 0)) {
if (sml_globs.ready) {
SML_Counter_Poll_1s();
}
}
break;
case FUNC_JSON_APPEND:
if (sml_globs.ready) {
if (sml_options & SML_OPTIONS_JSON_ENABLE) {
SML_Show(1);
}
}
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
if (sml_globs.ready) {
SML_Show(0);
}
break;
#endif // USE_WEBSERVER
case FUNC_COMMAND_SENSOR:
if (XSNS_53 == XdrvMailbox.index) {
result = XSNS_53_cmd();
}
break;
case FUNC_SAVE_BEFORE_RESTART:
case FUNC_SAVE_AT_MIDNIGHT:
if (sml_globs.ready) {
SML_CounterSaveState();
}
break;
}
return result;
}
#endif // USE_SML