%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(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_HP_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
|