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

#ifndef NO_USE_SML_TCP
// modbus over TCP
#define USE_SML_TCP
#endif

#ifndef NO_SML_OBIS_LINE
// obis in line mode
#define SML_OBIS_LINE
#endif

#ifdef ESP32
#ifndef NO_USE_SML_CANBUS
// canbus support
#undef USE_SML_CANBUS
#define USE_SML_CANBUS
#endif
#endif

#ifdef USE_SML_TCP_SECURE
#define USE_SML_TCP_IP_STR
#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


#ifdef USE_SML_CANBUS

#ifdef ESP8266
// esp8266 uses SPI MPC2515
#undef SML_CAN_MASKS
#undef SML_CAN_FILTERS
#define SML_CAN_MASKS 2
#define SML_CAN_FILTERS 6
#include "mcp2515.h"
#else
// esp32 uses native twai
#undef SML_CAN_MASKS
#undef SML_CAN_FILTERS
#define SML_CAN_MASKS 1
#define SML_CAN_FILTERS 1
#include <can.h>
#include "driver/twai.h"
#endif
#endif // USE_SML_CANBUS

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

7:
on esp32 the uart index may be set, normally it is allocated from 2 down to 0 automatically
thus you can combine serial SML with serial script , berry or serial drivers.

8:
on esp32 1 filter mask
on esp8266 2 filter masks

9:
on esp32 1 filter
on esp8266 6 filters

A:
decryption flags (8 bits)

*/

//#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 invert);
  int 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) {
#ifdef __riscv
  m_bit_time = 1000000 / speed;
#else
  m_bit_time = ESP.getCpuFreqMHz() * 1000000 / speed;
#endif
}

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, int32_t invert) {
  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, invert);
    }
  }
  return true;
}

void SML_ESP32_SERIAL::flush(void) {
  if (hws) {
    hws->flush();
  } else {
    m_in_pos = m_out_pos = 0;
  }
}

int 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
#ifdef __riscv
    ss_bstart = micros() - (m_bit_time / 4);
#else
    ss_bstart = ESP.getCycleCount() - (m_bit_time / 4);
#endif
    ss_byte = 0;
    ss_index++;
  } else {
    // now any bit changes go here
    // calc bit number
#ifdef __riscv
    diff = (micros() - ss_bstart) / m_bit_time;
#else
    diff = (ESP.getCycleCount() - ss_bstart) / m_bit_time;
#endif

    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;
      }
#ifdef __riscv
      ss_bstart = micros() - (m_bit_time / 4);
#else
      ss_bstart = ESP.getCycleCount() - (m_bit_time / 4);
#endif
      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;

typedef union {
  uint8_t data;
  struct {
    uint8_t SO_DWS74_BUG : 1;
    uint8_t SO_OBIS_LINE : 1;
    uint8_t SO_TRX_INVERT : 1;
  };
} SO_FLAGS;

#ifndef TMSBSIZ
#define TMSBSIZ 256
#endif

#ifndef SML_STIMEOUT
#define SML_STIMEOUT 1000
#endif

#define METER_ID_SIZE 24

#define SML_CRYPT_SIZE 16

#ifndef SML_PREFIX_SIZE
#define SML_PREFIX_SIZE 8
#endif

struct METER_DESC {
  int8_t srcpin;
  uint8_t type;
  uint16_t flag;
  int32_t params;
  char prefix[SML_PREFIX_SIZE];
  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;
  SO_FLAGS 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 // USE_SML_SPECOPT

#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 crypflags;
	uint8_t last_iob;
	uint8_t key[SML_CRYPT_SIZE];
	Han_Parser *hp;
#ifdef USE_SML_AUTHKEY
	uint8_t auth[SML_CRYPT_SIZE];
#endif // USE_SML_AUTHKEY
#endif // USE_SML_DECRYPT

#ifdef USE_SML_TCP

#ifdef USE_SML_TCP_IP_STR
  char ip_addr[16];
#else
  IPAddress ip_addr;
#endif // USE_SML_TCP_IP_STR

#ifdef USE_SML_TCP_SECURE
  WiFiClientSecure *client;
#else
  WiFiClient *client;
#endif // USE_SML_TCP_SECURE

#endif // USE_SML_TCP


#ifdef USE_SML_CANBUS
#ifdef ESP8266
  MCP2515 *mcp2515;
#else
  //twai_handle_t *canp;
#endif
  uint32_t can_masks[SML_CAN_MASKS];
  uint32_t can_filters[SML_CAN_FILTERS];
#endif // USE_SML_CANBUS

#ifdef ESP32
  int8_t uart_index;
#endif
};



#define TCP_MODE_FLG 0x7f

// Meter flags
#define PULLUP_FLG 0x01
#define ANALOG_FLG 0x02
#define MEDIAN_FILTER_FLG 0x10
#define NO_SYNC_FLG 0x20

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;
  uint8_t to_cnt;
  bool ready;
#ifdef USE_SML_CANBUS
  uint8_t twai_installed;
#endif // USE_SML_CANBUS
} 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;
  num--;
  if (meter_desc[num].srcpin != TCP_MODE_FLG) {
    if (!meter_desc[num].meter_ss) return 0;
    return meter_desc[num].meter_ss->available();
  } else {
    if (meter_desc[num].client) {
      return meter_desc[num].client->available();
    } else {
      return 0;
    }
  }
}

uint8_t Serial_read() {
  uint8_t num = sml_globs.dump2log & 7;
  if (num < 1 || num > sml_globs.meters_used) num = 1;
  num--;
  if (meter_desc[num].srcpin != TCP_MODE_FLG) {
    if (!meter_desc[num].meter_ss) return 0;
    return meter_desc[num].meter_ss->read();
  } else {
    if (meter_desc[num].client) {
      return meter_desc[num].client->read();
    } else {
      return 0;
    }
  }
}

uint8_t Serial_peek() {
  uint8_t num = sml_globs.dump2log & 7;
  if (num < 1 || num > sml_globs.meters_used) num = 1;
  num--;
  if (meter_desc[num].srcpin != TCP_MODE_FLG) {
    if (!meter_desc[num].meter_ss) return 0;
    return meter_desc[num].meter_ss->peek();
  } else {
    if (meter_desc[num].client) {
      return meter_desc[num].client->peek();
    } else {
      return 0;
    }
  }
}

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


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;

  struct METER_DESC *mp = &meter_desc[meter];

#ifdef USE_SML_DECRYPT
	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, mp->crypflags)) {
						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;
 #ifdef USE_SML_CANBUS       
      case 'C':
 #ifdef ESP8266     
        if (mp->mcp2515 == nullptr) break;
        { struct can_frame canFrame;
        while (mp->mcp2515->checkReceive()) {
            if (mp->mcp2515->readMessage(&canFrame) == MCP2515::ERROR_OK) {
              mp->sbuff[0] = canFrame.can_id >> 24;
              mp->sbuff[1] = canFrame.can_id >> 16;
              mp->sbuff[2] = canFrame.can_id >> 8;
              mp->sbuff[3] = canFrame.can_id;
              mp->sbuff[4] = canFrame.can_dlc;
              for (int i = 0; i < canFrame.can_dlc; i++) {
                mp->sbuff[5 + i] = canFrame.data[i];
              }
              sml_dump_start(' ');
              for (uint8_t index = 0; index < canFrame.can_dlc + 5; index++) {
                sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x", mp->sbuff[index]);
                sml_globs.sml_logindex += 2;
                if (index == 3) {
                  sml_globs.log_data[sml_globs.sml_logindex] = ':';
                  sml_globs.sml_logindex++;
                  sml_globs.log_data[sml_globs.sml_logindex] = ' ';
                  sml_globs.sml_logindex++;
                }
              }
              sml_globs.log_data[sml_globs.sml_logindex] = 0;
              AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
            } else {
              if (mp->mcp2515->checkError()) {
                uint8_t errFlags = mp->mcp2515->getErrorFlags();
                mp->mcp2515->clearRXnOVRFlags();
                AddLog(LOG_LEVEL_DEBUG, PSTR("SML CAN: Received error %d"), errFlags);
              }
            }
        }
        }
        break;
#else
        // esp32 native CAN
        if (!sml_globs.twai_installed) break;
        {
        uint32_t alerts_triggered = sml_can_check_alerts();

        // Check if message is received
        if (alerts_triggered & TWAI_ALERT_RX_DATA) {
          twai_message_t message;
          while (twai_receive(&message, 0) == ESP_OK) {
            mp->sbuff[0] = message.identifier >> 24;
            mp->sbuff[1] = message.identifier >> 16;
            mp->sbuff[2] = message.identifier >> 8;
            mp->sbuff[3] = message.identifier;
            mp->sbuff[4] = message.data_length_code;
            for (int i = 0; i < message.data_length_code; i++) {
              mp->sbuff[5 + i] = message.data[i];
            }
            sml_dump_start(' ');
            for (uint8_t index = 0; index < message.data_length_code + 5; index++) {
              sprintf(&sml_globs.log_data[sml_globs.sml_logindex], "%02x", mp->sbuff[index]);
              sml_globs.sml_logindex += 2;
              if (index == 3) {
                  sml_globs.log_data[sml_globs.sml_logindex] = ':';
                  sml_globs.sml_logindex++;
                  sml_globs.log_data[sml_globs.sml_logindex] = ' ';
                  sml_globs.sml_logindex++;
              }
            }
            sml_globs.log_data[sml_globs.sml_logindex] = 0;
            AddLogData(LOG_LEVEL_INFO, sml_globs.log_data);
          }
        }
        }
        break;
#endif
#endif // USE_SML_CANBUS
    	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];
            }
        }
    }

}

// check ebus escapes
uint8_t check_ebus_esc(uint8_t *ebus_buffer, unsigned char len) {
    short count,count1;
    count1 = 0;
    for (count = 0; count < len; count++) {
        if (ebus_buffer[count] == EBUS_ESC) {
            //found escape
            count1++;
        }
    }
    return count1;
}

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, mp->crypflags)) {
				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;
  if (mp->srcpin != TCP_MODE_FLG) {
    iob = (uint8_t)mp->meter_ss->read(); 
  } else {
    if (mp->client) {
      iob = (uint8_t)mp->client->read();
    } else {
      iob = 0;
    }
  }

  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;
      if (mp->sbuff[0] != SML_SYNC && ((mp->flag & NO_SYNC_FLG) == 0)) {
        // Skip decoding, when buffer does not start with sync byte (0x77)
        sb_counter++;
        return;
      }
      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;
        SML_Decode(meters);
				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->srcpin == TCP_MODE_FLG) {
        // tcp read
        if (mp->spos >= 6) {
          uint8_t tlen = (mp->sbuff[4] << 8) | mp->sbuff[5];
          if (mp->spos == 6 + tlen) {
            mp->spos = 0;
            memmove(&mp->sbuff[0], &mp->sbuff[6], mp->sbsiz - 6);
            SML_Decode(meters);
            if (mp->client) {
              mp->client->flush();
            }
            //Hexdump(mp->sbuff + 6, 10);
          }
        }
        break;
      }

      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 > 5 && mp->spos > mp->sbuff[4] + 5) {
          // get telegramm lenght
          uint16_t tlen = mp->sbuff[4] + 5 + check_ebus_esc(mp->sbuff, mp->spos);
          // test crc
          if (mp->sbuff[tlen] == ebus_CalculateCRC(mp->sbuff, tlen)) {
              ebus_esc(mp->sbuff, mp->spos);
              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++) {
      struct METER_DESC *mp = &meter_desc[meters];
      if (mp->type == 'C') continue;
      if (mp->type != 'c') {
        if (mp->srcpin != TCP_MODE_FLG) {
          if (!mp->meter_ss) continue;
          // poll for serial input
          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, mp->meter_ss->available() && !digitalRead(sml_globs.ser_act_LED_pin)); // Invert LED, if queue is continuously full
          }
          while (mp->meter_ss->available()) {
            sml_shift_in(meters, 0);
          }
        } else {
#ifdef USE_SML_TCP
          if (mp->client) {    
            while (mp->client->available()){
              sml_shift_in(meters, 0);
            }
          }
#endif
        }
      }
    }
}

#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 & MEDIAN_FILTER_FLG) {
                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 (found == 0) {
          // skip rest of decoder part
          mp++;
          continue;
        }
        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
										}
									} else {
                    found = 0;
                  }
									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)) {
                  uint32_t skip = strtol((char*)mp, (char**)&mp, 10);
                  cp += skip;
                }
              }
            } else if (!strncmp(mp, "U64", 3)) {
              uint32_t valh = (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | (cp[3]<<0);
              uint32_t vall = (cp[4]<<24) | (cp[5]<<16) | (cp[6]<<8) | (cp[7]<<0);
              uint64_t val = ((uint64_t)valh<<32) | vall;
              mp += 3;
              cp += 8;
              ebus_dval = val;
              mbus_dval = val;
            } else if (!strncmp(mp, "u64", 3)) {
              uint64_t valh = (cp[1]<<24) | (cp[0]<<16) | (cp[3]<<8) | (cp[2]<<0);
              uint64_t vall = (cp[5]<<24) | (cp[4]<<16) | (cp[7]<<8) | (cp[6]<<0);
              uint64_t val = ((uint64_t)valh<<32) | vall;
              mp += 3;
              cp += 8;
              ebus_dval = val;
              mbus_dval = val;
            } else if (!strncmp(mp, "U32", 3)) {
              mp += 3;
              goto U32_do;
            } else if (!strncmp(mp, "UUuuUUuu", 8)) {
              mp += 8;
              U32_do:
              uint32_t val = (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | (cp[3]<<0);
              cp += 4;
              if (*mp == 's') {
                mp++;
                // swap words
                val = (val>>16) | (val<<16);
              }
              ebus_dval = val;
              mbus_dval = val;
            } else if (!strncmp(mp, "u32", 3)) {
              mp += 3;
              goto u32_do;
            } else if (!strncmp(mp, "uuUUuuUU", 8)) {
              mp += 8;
              u32_do:
              uint32_t val = (cp[1]<<24) | (cp[0]<<16) | (cp[3]<<8) | (cp[2]<<0);
              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, "S32", 3)) {
              mp += 3;
              goto S32_do;
            } else if (!strncmp(mp, "SSssSSss", 8)) {
              mp += 8;
              S32_do:
              int32_t val = (cp[0]<<24) | (cp[1]<<16) | (cp[2]<<8) | (cp[3]<<0);
              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, "s32", 3)) {
              mp += 3;
              goto s32_do;
            } else if (!strncmp(mp, "ssSSssSS", 8)) {
              mp += 8;
              s32_do:
              int32_t val = (cp[1]<<24) | (cp[0]<<16) | (cp[3]<<8) | (cp[2]<<0);
              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 != 'C' && 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 if (*mp == 's') {
                  mp++;
                  char delim = *mp;
                  mp++;
                  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, delim);
                      if (!bp) {
                        break;
                      }
                      bp++;
                      lcp = bp;
                    }
                  }
                  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 {
                if (meter_desc[mindex].srcpin != TCP_MODE_FLG) {
                  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 & MEDIAN_FILTER_FLG) {
            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_P(PSTR("<tr><td colspan=2>%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 == 's') {
            // skip values
            if (sml_globs.mp[mindex].type == 'o') {
              cp += 2;
              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_P(PSTR("{s}%s %s{m}"), sml_globs.mp[mindex].prefix, name);  // Do not replace decimal separator in label
                WSContentSend_PD(PSTR("%s %s{e}"), tpowstr, unit); // Replace decimal separator in value
              }
            }
          }
        }
        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);
void 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.data = 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;
#endif // USE_SML_AUTHKEY
    case 'A':
      cp += 2;
      mp->crypflags = strtol(cp, &cp, 10);
      break;
#endif // USE_SML_DECRYPT
		case '6':
			cp += 2;
			mp->tout_ms = strtol(cp, &cp, 10);
      break;
  	case '7':
			cp += 2;
#ifdef ESP32     
			mp->uart_index = strtol(cp, &cp, 10);
#endif // ESP32
			break;

#ifdef USE_SML_CANBUS
     case '8':
      cp += 2;
      for (uint8_t cnt = 0; cnt < SML_CAN_MASKS; cnt++) {
				mp->can_masks[cnt] = sml_hex32(cp);
        cp += 8;
        if (*cp != ',') {
          break;
        }
        cp++;
			}
      break;
    case '9':
      cp += 2;
      for (uint8_t cnt = 0; cnt < SML_CAN_FILTERS; cnt++) {
				mp->can_filters[cnt] = sml_hex32(cp);
        cp += 8;
        if (*cp != ',') {
          break;
        }
        cp++;
			}
      break;

#endif // USE_SML_CANBUS
	}
	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.data = 0;
    // addresses a bug in meter DWS74
#ifdef DWS74_BUG
    mp->so_flags.SO_DWS74_BUG = 1;
#endif

#ifdef SML_OBIS_LINE
    mp->so_flags.SO_OBIS_LINE = 1;
#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 ESP32
    mp->uart_index = -1;
#endif

#ifdef USE_SML_CANBUS
    for (uint8_t cnt = 0; cnt < SML_CAN_MASKS; cnt++) {
			mp->can_masks[cnt] = 0;
		}
    for (uint8_t cnt = 0; cnt < SML_CAN_FILTERS; cnt++) {
			mp->can_filters[cnt] = 0;
    }
#endif // USE_SML_CANBUS

#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

#ifdef USE_SML_CANBUS
#ifdef ESP32
    if (sml_globs.twai_installed) {
      twai_stop();
      twai_driver_uninstall();
      sml_globs.twai_installed = false;
    }
#endif
#endif // USE_SML_CANBUS

    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;

#ifdef ESP32
  uint32_t uart_index = SOC_UART_NUM - 1;
#endif

  sml_globs.sml_send_blocks = 0;
  lp = glob_script_mem.section_ptr;
  struct METER_DESC *mmp;
  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++;
          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
          index = *lp1 & 7;
          lp1 += 2;
          if (index < 1 || index > sml_globs.meters_used) {
            AddLog(LOG_LEVEL_INFO, PSTR("illegal meter number!"));
            goto next_line;
          }
          index--;
          mmp = &meter_desc[index];
          if (*lp1 == '[') {
            // sign TCP mode
            srcpin = TCP_MODE_FLG;
            lp1++;
            char str[32];
            uint8_t cnt;
            for (cnt = 0; cnt < sizeof(str) - 1; cnt++) {
              if (!*lp1 || *lp1 == '\n' || *lp1 == ']') {
                break;
              }
              str[cnt] = *lp1++;
            }
            str[cnt] = 0;
            lp1++;
#ifdef USE_SML_TCP
#ifdef USE_SML_TCP_IP_STR
            strcpy(mmp->ip_addr, str);
#else
            mmp->ip_addr.fromString(str);
#endif
#endif
          } else {
            srcpin  = strtol(lp1, &lp1, 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;
            }
          }
          mmp->srcpin = srcpin;
          if (*lp1 != ',') goto next_line;
          lp1++;
          mmp->type = *lp1;
          lp1++;
          if (*lp1 != ',') {
            switch (*lp1) {
              case 'N':
                lp1++;
                mmp->sopt = 0x10 | (*lp1 & 3);
                lp1++;
                break;
              case 'E':
                lp1++;
                mmp->sopt = 0x20 | (*lp1 & 3);
                lp1++;
                break;
              case 'O':
                lp1++;
                mmp->sopt = 0x30 | (*lp1 & 3);
                lp1++;
                break;
              default:
                mmp->sopt = *lp1&7;
                lp1++;
            }
          } else {
            mmp->sopt = 0;
          }
          lp1++;
          mmp->flag = strtol(lp1, &lp1, 10);
          if (*lp1 != ',') goto next_line;
          lp1++;
          mmp->params = strtol(lp1, &lp1, 10);
          if (*lp1 != ',') goto next_line;
          lp1++;
          mmp->prefix[SML_PREFIX_SIZE - 1] = 0;
          for (uint32_t cnt = 0; cnt < SML_PREFIX_SIZE; cnt++) {
            if (*lp1 == SCRIPT_EOL || *lp1 == ',') {
              mmp->prefix[cnt] = 0;
              break;
            }
           mmp->prefix[cnt] = *lp1++;
          }
          if (*lp1 == ',') {
            lp1++;
            // get TRX pin
            mmp->trxpin = strtol(lp1, &lp1, 10);
            if (mmp->srcpin != TCP_MODE_FLG) {
              if (Gpio_used(mmp->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 (*lp1 == '(') {
              lp1++;
              if (*lp1 == 'i') {
                lp1++;
                mmp->trx_en.trxenpol = 1;
              } else {
                mmp->trx_en.trxenpol = 0;
              }
              mmp->trx_en.trxenpin = strtol(lp1, &lp1, 10);
              if (*lp1 != ')') {
                goto dddef_exit;
              }
              lp1++;
              if (Gpio_used(mmp->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;
              }
              mmp->trx_en.trxen = 1;
              pinMode(mmp->trx_en.trxenpin, OUTPUT);
              digitalWrite(mmp->trx_en.trxenpin, mmp->trx_en.trxenpol);
            } else {
              mmp->trx_en.trxen = 0;
            }
            if (*lp1 != ',') goto next_line;
            lp1++;
            mmp->tsecs = strtol(lp1, &lp1, 10);
            if (*lp1 == ',') {
              lp1++;
              // look ahead
              uint16_t txlen = 0;
              uint16_t tx_entries = 1;
              char *txp = lp1;
              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) {
                mmp->txmem = (char*)calloc(txlen + 2, 1);
								memory += txlen + 2;
                if (mmp->txmem) {
                  // now copy send blocks
                  char *txp = lp1;
                  uint16_t tind = 0;
                  for (uint32_t cnt = 0; cnt < txlen; cnt++) {
                      if (*txp == SCRIPT_EOL) {
                        txp++;
                      } else {
                        mmp->txmem[tind] = *txp++;
                        tind++;
                      }
                  }
                }
                //AddLog(LOG_LEVEL_INFO, PSTR(">>> %s - %d"), meter_desc[index].txmem, txlen);
                mmp->index = 0;
                mmp->max_index = tx_entries;
                sml_globs.sml_send_blocks++;
                lp1 += txlen;
              }
            }
          }
          if (*lp1 == SCRIPT_EOL) lp1--;
          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();
  }

  sml_counter_pinstate = 0;
  for (uint8_t meters = 0; meters < sml_globs.meters_used; meters++) {
    METER_DESC *mp = &meter_desc[meters];
    if (mp->type == 'c') {
        if (mp->flag & ANALOG_FLG) {

        } else {
          // counters, set to input with pullup
          if (mp->flag & PULLUP_FLG) {
            pinMode(mp->srcpin, INPUT_PULLUP);
          } else {
            pinMode(mp->srcpin, INPUT);
          }
          // check for irq mode
          if (mp->params <= 0) {
            // init irq mode
            sml_counters[cindex].sml_cnt_old_state = meters;
            sml_counters[cindex].sml_debounce = -sml_globs.mp[meters].params;
            attachInterruptArg(mp->srcpin, SML_CounterIsr, &sml_cnt_index[cindex], CHANGE);
            if (digitalRead(mp->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 if (mp->type == 'C') {
#ifdef USE_SML_CANBUS

#ifdef ESP8266
      mp->mcp2515 = nullptr;
      if ( PinUsed(GPIO_SPI_MISO) && PinUsed(GPIO_SPI_MOSI) && PinUsed(GPIO_SPI_CLK) ) {
        mp->mcp2515 = new MCP2515(mp->srcpin);
        if (MCP2515::ERROR_OK != mp->mcp2515->reset()) {
          AddLog(LOG_LEVEL_DEBUG, PSTR("SML CAN: Failed to reset module"));
          return;
        }

        if (MCP2515::ERROR_OK != mp->mcp2515->setBitrate((CAN_SPEED)(mp->params%100), (CAN_CLOCK)(mp->params/100))) {
          AddLog(LOG_LEVEL_DEBUG, PSTR("SML CAN: Failed to set module bitrate"));
          return;
        }

        //attachInterrupt(mp->trxpin, sml_canbus_irq, FALLING);

        if (MCP2515::ERROR_OK != mp->mcp2515->setConfigMode()) {
          AddLog(LOG_LEVEL_DEBUG, PSTR("SML CAN: Failed to set config mode"));
        } else {
          if (mp->can_filters[0]) mp->mcp2515->setFilter(MCP2515::RXF0, true, mp->can_filters[0]);
          if (mp->can_filters[1]) mp->mcp2515->setFilter(MCP2515::RXF1, true, mp->can_filters[1]);
          if (mp->can_filters[2]) mp->mcp2515->setFilter(MCP2515::RXF2, true, mp->can_filters[2]);
          if (mp->can_filters[3]) mp->mcp2515->setFilter(MCP2515::RXF3, true, mp->can_filters[3]);
          if (mp->can_filters[4]) mp->mcp2515->setFilter(MCP2515::RXF4, true, mp->can_filters[4]);
          if (mp->can_filters[5]) mp->mcp2515->setFilter(MCP2515::RXF5, true, mp->can_filters[5]);

          if (mp->can_masks[0]) mp->mcp2515->setFilterMask(MCP2515::MASK0, true, mp->can_masks[0]);
          if (mp->can_masks[1]) mp->mcp2515->setFilterMask(MCP2515::MASK1, true, mp->can_masks[1]);

         }

        if (MCP2515::ERROR_OK != mp->mcp2515->setNormalMode()) {
          AddLog(LOG_LEVEL_DEBUG, PSTR("SML CAN: Failed to set normal mode"));
          return;
        }

        AddLog(LOG_LEVEL_INFO, PSTR("SML CAN: Initialized"));
      } else {
        AddLog(LOG_LEVEL_DEBUG, PSTR("SML CAN: SPI not configuered"));
      }
 #else
      // Initialize configuration structures using macro initializers
      twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)mp->trxpin, (gpio_num_t)mp->srcpin, TWAI_MODE_NORMAL);
      uint8_t qlen = mp->params/100;
      if (qlen < 8) {
        qlen = 8;
      }
      g_config.rx_queue_len = qlen;
      twai_timing_config_t t_config;
      switch (mp->params%100) {
        case 0:
          t_config = TWAI_TIMING_CONFIG_25KBITS();
          break;
        case 1:
          t_config = TWAI_TIMING_CONFIG_50KBITS();
          break;
        case 2:
          t_config = TWAI_TIMING_CONFIG_100KBITS();
          break;
        case 3:
          t_config = TWAI_TIMING_CONFIG_125KBITS();
          break;
        case 4:
          t_config = TWAI_TIMING_CONFIG_250KBITS();
          break;
        case 5:
          t_config = TWAI_TIMING_CONFIG_500KBITS();
          break;
        case 6:
          t_config = TWAI_TIMING_CONFIG_800KBITS();
          break;
        case 7:
          t_config = TWAI_TIMING_CONFIG_1MBITS();
          break;
        default:
          t_config = TWAI_TIMING_CONFIG_125KBITS();
          break;
      }
    
      twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();

      if (mp->can_filters[0]) {
        f_config.acceptance_code = mp->can_filters[0] << 3; 
        f_config.acceptance_mask = mp->can_masks[0] << 3; 
        f_config.single_filter = true;
      }
      sml_globs.twai_installed = false;
      // Install TWAI driver
      if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) {
        AddLog(LOG_LEVEL_DEBUG, PSTR("Can driver installed"));
        // Start TWAI driver
        if (twai_start() == ESP_OK) {
          AddLog(LOG_LEVEL_DEBUG, PSTR("Can driver started"));
          // Reconfigure alerts to detect frame receive, Bus-Off error and RX queue full states
          uint32_t alerts_to_enable = TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR;
          if (twai_reconfigure_alerts(alerts_to_enable, NULL) == ESP_OK) {
            AddLog(LOG_LEVEL_DEBUG, PSTR("CAN Alerts reconfigured"));
            AddLog(LOG_LEVEL_INFO, PSTR("Can driver ready"));
            sml_globs.twai_installed = true;
          } else {
            AddLog(LOG_LEVEL_DEBUG, PSTR("Failed to reconfigure CAN alerts"));
          } 
        } else {
          AddLog(LOG_LEVEL_DEBUG, PSTR("Failed to start can driver"));
        }
      } else {
        AddLog(LOG_LEVEL_DEBUG, PSTR("Failed to install can driver"));
      }
 #endif     
#endif // USE_SML_CANBUS
    } else {
      // serial input, init
      if (mp->srcpin == TCP_MODE_FLG) {
#ifdef USE_SML_TCP
        sml_tcp_init(mp);
#endif
      } else {
        // serial mode
#ifdef ESP8266
#ifdef SPECIAL_SS
        char type = mp->type;
        if (type == 'm' || type == 'M' || type == 'k' || type == 'p' || type == 'R' || type == 'v') {
          mp->meter_ss = new TasmotaSerial(mp->srcpin, mp->trxpin, 1, 0, mp->sibsiz);
        } else {
          mp->meter_ss = new TasmotaSerial(mp->srcpin, mp->trxpin, 1, 1, mp->sibsiz);
        }
#else
        mp->meter_ss = new TasmotaSerial(mp->srcpin, mp->trxpin, 1, 0, mp->sibsiz);
#endif  // SPECIAL_SS
#endif // ESP8266

#ifdef ESP32
        // use hardware serial
        if (mp->uart_index >= 0) {
          uart_index = mp->uart_index;
        }
        AddLog(LOG_LEVEL_INFO, PSTR("SML: uart used: %d"),uart_index);
#ifdef USE_ESP32_SW_SERIAL
        mp->meter_ss = new SML_ESP32_SERIAL(uart_index);
        if (mp->srcpin >= 0) {
          if (uart_index == 0) { ClaimSerial(); }
          uart_index--;
          if (uart_index < 0) uart_index = 0;
        }
#else
        mp->meter_ss = new HardwareSerial(uart_index);
        if (uart_index == 0) { ClaimSerial(); }
        uart_index--;
        if (uart_index < 0) uart_index = 0;
        mp->meter_ss->setRxBufferSize(mp->sibsiz);
#endif // USE_ESP32_SW_SERIAL

#endif  // ESP32

        uint32_t smode = SERIAL_8N1;

        if (mp->sopt & 0xf0) {
          // new serial config
          switch (mp->sopt >> 4) {
            case 1:
              if ((mp->sopt & 1) == 1) smode = SERIAL_8N1;
              else smode = SERIAL_8N2;
              break;
            case 2:
              if ((mp->sopt & 1) == 1) smode = SERIAL_8E1;
              else smode = SERIAL_8E2;
              break;
            case 3:
              if ((mp->sopt & 1) == 1) smode = SERIAL_8O1;
              else smode = SERIAL_8O2;
              break;
          }
        } else {
          // deprecated serial config
          if (mp->sopt == 2) {
            smode = SERIAL_8N2;
          }
          if (mp->type=='M') {
            smode = SERIAL_8E1;
            if (mp->sopt == 2) {
              smode = SERIAL_8E2;
            }
          }
        }

#ifdef ESP8266
        if (mp->meter_ss->begin(mp->params)) {
          mp->meter_ss->flush();
        }
        if (mp->meter_ss->hardwareSerial()) {
          Serial.begin(mp->params, (SerialConfig)smode);
          ClaimSerial();
          if (mp->so_flags.SO_TRX_INVERT) {
            U0C0 = U0C0 | BIT(UCRXI) | BIT(UCTXI); // Inverse RX, TX
          }
        }
#endif  // ESP8266

#ifdef ESP32
        mp->meter_ss->begin(mp->params, smode, mp->srcpin, mp->trxpin, mp->so_flags.SO_TRX_INVERT);
#ifdef USE_ESP32_SW_SERIAL
				mp->meter_ss->setRxBufferSize(mp->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
#ifdef USE_SML_MEDIAN_FILTER
   || !sml_globs.sml_mf
#endif
  ) {
    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
      mp->crypflags = 0;
		}
#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].type != 'C') {
    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++;
    uint32_t 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, (SerialConfig)smode);
#else
    meter_desc[meter].meter_ss->begin(baud, smode, sml_globs.mp[meter].srcpin, sml_globs.mp[meter].trxpin, sml_globs.mp[meter].so_flags.SO_TRX_INVERT);
#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;
}

uint32_t SML_Shift_Num(uint32_t meter, uint32_t shift) {
  struct METER_DESC *mp = &sml_globs.mp[meter];
  if (shift > mp->sbsiz) shift = mp->sbsiz;
  for (uint16_t cnt = 0; cnt < shift; cnt++) {
     for (uint16_t count = 0; count < mp->sbsiz - 1; count++) {
      mp->sbuff[count] = mp->sbuff[count + 1];
      SML_Decode(meter);
    }
  }
  return shift;
}


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].type != 'C') {
    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);
		}
	}
}


#ifndef CNT_PULSE_TIMEOUT
#define CNT_PULSE_TIMEOUT 5000
#endif

// 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 & ANALOG_FLG) {
            // 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

#ifdef USE_SML_CANBUS


#ifdef ESP32
#define POLLING_RATE_MS 100
uint32_t sml_can_check_alerts() {

  uint32_t alerts_triggered;
  twai_read_alerts(&alerts_triggered, pdMS_TO_TICKS(POLLING_RATE_MS));
  twai_status_info_t twaistatus;
  twai_get_status_info(&twaistatus);

  // Handle alerts
  if (alerts_triggered & TWAI_ALERT_ERR_PASS) {
    AddLog(LOG_LEVEL_DEBUG, PSTR("Alert: TWAI controller has become error passive."));
  }
  if (alerts_triggered & TWAI_ALERT_BUS_ERROR) {
    AddLog(LOG_LEVEL_DEBUG, PSTR("Alert: A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus."));
    AddLog(LOG_LEVEL_DEBUG, PSTR("Bus error count: %d"), twaistatus.bus_error_count);
  }
  if (alerts_triggered & TWAI_ALERT_RX_QUEUE_FULL) {
    AddLog(LOG_LEVEL_DEBUG, PSTR("Alert: The RX queue is full causing a received frame to be lost."));
    AddLog(LOG_LEVEL_DEBUG, PSTR("RX buffered: %d"), twaistatus.msgs_to_rx);
    AddLog(LOG_LEVEL_DEBUG, PSTR("RX missed: %d"), twaistatus.rx_missed_count);
    AddLog(LOG_LEVEL_DEBUG, PSTR("RX overrun %d"), twaistatus.rx_overrun_count);
  }

  if (alerts_triggered & TWAI_ALERT_TX_FAILED) {
    AddLog(LOG_LEVEL_DEBUG, PSTR("Alert: The Transmission failed."));
    AddLog(LOG_LEVEL_DEBUG, PSTR("TX buffered: %d"), twaistatus.msgs_to_tx);
    AddLog(LOG_LEVEL_DEBUG, PSTR("TX error: %d"), twaistatus.tx_error_counter);
    AddLog(LOG_LEVEL_DEBUG, PSTR("TX failed: %d"), twaistatus.tx_failed_count);
  }
  
  if (alerts_triggered & TWAI_ALERT_TX_SUCCESS) {
    AddLog(LOG_LEVEL_DEBUG, PSTR("Alert: The Transmission was successful."));
    AddLog(LOG_LEVEL_DEBUG, PSTR("TX buffered: %d"), twaistatus.msgs_to_tx);
  }

  return alerts_triggered;
}

#endif // ESP32


#define SML_CAN_MAX_FRAMES 8

void SML_CANBUS_Read() {
#ifdef ESP8266
  struct can_frame canFrame;

  for (uint32_t meter = 0; meter < sml_globs.meters_used; meter++) {
    struct METER_DESC *mp = &sml_globs.mp[meter];
    uint8_t nCounter = 0;

    if (mp->type != 'C') continue;

    if (mp->mcp2515 == nullptr) continue;

    while (mp->mcp2515->checkReceive() && nCounter <= SML_CAN_MAX_FRAMES) {
      if (mp->mcp2515->readMessage(&canFrame) == MCP2515::ERROR_OK) {
          mp->sbuff[0] = canFrame.can_id >> 24;
          mp->sbuff[1] = canFrame.can_id >> 16;
          mp->sbuff[2] = canFrame.can_id >> 8;
          mp->sbuff[3] = canFrame.can_id;
          mp->sbuff[4] = canFrame.can_dlc;
          for (int i = 0; i < canFrame.can_dlc; i++) {
            mp->sbuff[5 + i] = canFrame.data[i];
          }
          SML_Decode(meter);
          nCounter++;
      } else {
        if (mp->mcp2515->checkError()) {
          uint8_t errFlags = mp->mcp2515->getErrorFlags();
          mp->mcp2515->clearRXnOVRFlags();
          AddLog(LOG_LEVEL_DEBUG, PSTR("SML CAN: Received error %d"), errFlags);
          break;
        }
      }
    }
  }
#else

  for (uint32_t meter = 0; meter < sml_globs.meters_used; meter++) {
    struct METER_DESC *mp = &sml_globs.mp[meter];
    uint8_t nCounter = 0;

    if (mp->type != 'C') continue;

    if (sml_globs.twai_installed) {
        uint32_t alerts_triggered = sml_can_check_alerts();

        // Check if message is received
        if (alerts_triggered & TWAI_ALERT_RX_DATA) {
          // One or more messages received. Handle all.
          twai_message_t message;
          while (twai_receive(&message, 0) == ESP_OK) {
            mp->sbuff[0] = message.identifier >> 24;
            mp->sbuff[1] = message.identifier >> 16;
            mp->sbuff[2] = message.identifier >> 8;
            mp->sbuff[3] = message.identifier;
            mp->sbuff[4] = message.data_length_code;
            for (int i = 0; i < message.data_length_code; i++) {
              mp->sbuff[5 + i] = message.data[i];
            }
            SML_Decode(meter);
          }
        }
        
    }
  } 

#endif
}
#endif // USE_SML_CANBUS

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 || meter_desc[cnt].script_str)) {
      //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;
}

uint32_t sml_hex32(char *cp) {
  uint32_t iob = (sml_hexnibble(*cp++) << 4) | sml_hexnibble(*cp++);
  uint32_t result = iob << 24;
  iob = (sml_hexnibble(*cp++) << 4) | sml_hexnibble(*cp++);
  result |= iob << 16;
  iob = (sml_hexnibble(*cp++) << 4) | sml_hexnibble(*cp++);
  result |= iob << 8;
  iob = (sml_hexnibble(*cp++) << 4) | sml_hexnibble(*cp++);
  result |= iob;
  return result;
}

typedef struct {
  uint16_t T_ID;
  uint16_t P_ID;
  uint16_t SIZE;
  uint8_t U_ID;
  uint8_t payload[8];
 } MODBUS_TCP_HEADER;

uint16_t sml_swap(uint16_t in) {
  return (in << 8) | in >> 8;
}

// send modbus TCP frame with payload
// given ip addr  and port in baudrate
void sml_tcp_send(uint32_t meter, uint8_t *sbuff, uint16_t slen) {
MODBUS_TCP_HEADER tcph;

  //tcph.T_ID = sml_swap(0x1234);
  tcph.T_ID = random(0xffff);

  tcph.P_ID = 0;
  tcph.SIZE = sml_swap(6);
  tcph.U_ID = *sbuff;

  sbuff++;
  for (uint8_t cnt = 0; cnt < slen - 3; cnt++) {
    tcph.payload[cnt] = *sbuff++;
  }

#ifdef USE_SML_TCP
  // AddLog(LOG_LEVEL_INFO, PSTR("slen >> %d "),slen);
  if (meter_desc[meter].client) {
    if (meter_desc[meter].client->connected()) {
      meter_desc[meter].client->write((uint8_t*)&tcph, 7 + slen - 3);
    }
  }
#endif
}

#ifdef USE_SML_TCP
int32_t sml_tcp_init(struct METER_DESC *mp) {  
  if (!TasmotaGlobal.global_state.wifi_down) {
    if (!mp->client) {
      // tcp mode
#ifdef USE_SML_TCP_SECURE
      mp->client = new WiFiClientSecure;
      //client(new BearSSL::WiFiClientSecure_light(1024,1024)) {
      mp->client->setInsecure();
#else        
      mp->client = new WiFiClient;
#endif // USE_SML_TCP_SECURE
    }
    int32_t err = mp->client->connect(mp->ip_addr, mp->params);
    char ipa[32];
#ifdef USE_SML_TCP_IP_STR
    strcpy(ipa, mp->ip_addr);
#else
    strcpy(ipa, mp->ip_addr.toString().c_str());
#endif
    if (!err) {
      AddLog(LOG_LEVEL_INFO, PSTR("SML: could not connect TCP to %s:%d"),ipa, mp->params);
    } else {
      AddLog(LOG_LEVEL_INFO, PSTR("SML: connected TCP to %s:%d"),ipa, mp->params);
    }
  } else {
    AddLog(LOG_LEVEL_INFO, PSTR("SML: could not connect TCP since wifi is down"));
    mp->client = nullptr;
    return -1;
  }
  return 0;
}

#ifndef TCP_TIMEOUT
#define TCP_TIMEOUT 30
#endif

void sml_tcp_check(void) {
  sml_globs.to_cnt++;
  if (sml_globs.to_cnt > TCP_TIMEOUT) {
    sml_globs.to_cnt = 0;
    for (uint32_t meter = 0; meter < sml_globs.meters_used; meter++) {
      struct METER_DESC *mp = &sml_globs.mp[meter];
		  if (mp->srcpin == TCP_MODE_FLG) {
			  if (!mp->client) {
          sml_tcp_init(mp);
        } else {
          if (!mp->client->connected()) {
            sml_tcp_init(mp);
          }
        }
		  }
	  }
  }
}


#endif // USE_SML_TCP


// 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++;
  }

  struct METER_DESC *mp = &meter_desc[meter];
  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 (mp->type == 'm' || mp->type == 'M' || mp->type == 'k') {
    if (mp->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 (mp->type == 'o') {
    for (uint32_t cnt = 0; cnt < slen; cnt++) {
      sbuff[cnt] |= (CalcEvenParity(sbuff[cnt]) << 7);
    }
  }
  if (mp->type == 'p') {
    *ucp++ = 0xc0;
    *ucp++ = 0xa8;
    *ucp++ = 1;
    *ucp++ = 1;
    *ucp++ = 0;
    *ucp++ = SML_PzemCrc(sbuff, 6);
    slen += 6;
  }

  if (mp->srcpin == TCP_MODE_FLG) {
    sml_tcp_send(meter, sbuff, slen);
  } else {
    if (mp->type == 'C') {
#ifdef USE_SML_CANBUS
#ifdef ESP8266
      if (mp->mcp2515 != nullptr) {
        struct can_frame canMsg;
        canMsg.can_id = (uint32_t) (sbuff[0] << 24 | sbuff[1] << 16 | sbuff[2] << 8 | sbuff[3]);
        canMsg.can_dlc = sbuff[4];
        for (uint8_t i = 0; i < canMsg.can_dlc; i++) {
          canMsg.data[i] = sbuff[i + 5];
        }
        mp->mcp2515->sendMessage(&canMsg);
      }
#else
      if (sml_globs.twai_installed) {
        twai_message_t message;
        message.identifier = (uint32_t) (sbuff[0] << 24 | sbuff[1] << 16 | sbuff[2] << 8 | sbuff[3]);
        message.data_length_code = sbuff[4];
        for (uint8_t i = 0; i < message.data_length_code; i++) {
          message.data[i] = sbuff[i + 5];
        }

        message.flags = 0;
        if (message.identifier & 0x80000000) {
          message.extd = 1;
          message.identifier &= 0x7fffffff;
        }

        twai_clear_receive_queue();

        // Queue message for transmission
        if (twai_transmit(&message, pdMS_TO_TICKS(100)) == ESP_OK) {
          AddLog(LOG_LEVEL_DEBUG, PSTR("Can message queued for transmission"));
        } else {
          AddLog(LOG_LEVEL_DEBUG, PSTR("Failed to queue can message for transmission"));
        }
      }
#endif
#endif // USE_SML_CANBUS
    } else { 
      if (mp->trx_en.trxen) {
        digitalWrite(meter_desc[meter].trx_en.trxenpin, meter_desc[meter].trx_en.trxenpol ^ 1);
      }
      mp->meter_ss->flush();
      mp->meter_ss->write(sbuff, slen);
      if (mp->trx_en.trxen) {
        // must wait for all data sent
        mp->meter_ss->flush();
        digitalWrite(mp->trx_en.trxenpin, mp->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' || type == 'C') {
      Hexdump(sbuff, slen);
    }
#endif
  }

#ifdef MODBUS_DEBUG
  uint8_t type = mp->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 (bitRead(Settings->rule_enabled, 0)) {
          if (sml_globs.ready) {
            SML_Counter_Poll();
            if (sml_globs.dump2log) {
              dump2log();
            } else {
              SML_Poll();
#ifdef USE_SML_CANBUS
              SML_CANBUS_Read();
#endif// USE_SML_CANBUS
            }
          }
        }
        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();
#ifdef USE_SML_TCP
              sml_tcp_check();
#endif
						}
					}
					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
