diff --git a/sonoff/xsns_53_sml.ino b/sonoff/xsns_53_sml.ino index bb8ba1e4c..77c88bd36 100644 --- a/sonoff/xsns_53_sml.ino +++ b/sonoff/xsns_53_sml.ino @@ -22,7 +22,6 @@ */ #ifdef USE_SML_M -// #define XSNS_53 53 @@ -30,13 +29,14 @@ #define SML_BAUDRATE 9600 // send this every N seconds (for meters that only send data on demand) +// not longer supported, use scripting instead //#define SML_SEND_SEQ // debug counter input to led for counter1 and 2 //#define DEBUG_CNT_LED1 2 //#define DEBUG_CNT_LED1 2 -// use analog optical counter sensor with AD Converter ADS1115 (not yet) +// use analog optical counter sensor with AD Converter ADS1115 (not yet functional) //#define ANALOG_OPTO_SENSOR // fototransistor with pullup at A0, A1 of ADS1115 A3 and +3.3V // level and amplification are automatically set @@ -44,7 +44,7 @@ #include -// use special no wait serial driver +// use special no wait serial driver, should be always on #define SPECIAL_SS // addresses a bug in meter DWS74 @@ -147,6 +147,9 @@ struct METER_DESC { uint8_t max_index; }; +// this descriptor method is no longer supported +// but still functional for simple meters +// use scripting method instead // meter list , enter new meters here //===================================================== #define EHZ161_0 1 @@ -391,7 +394,7 @@ const uint8_t meter[]= #define METERS_USED 1 struct METER_DESC const meter_desc[METERS_USED]={ [0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; -// 2 Richtungszähler EHZ SML 8 bit 9600 baud, binär +// 2 direction meter EHZ SML 8 bit 9600 baud, binary const uint8_t meter[]= //0x77,0x07,0x01,0x00,0x01,0x08,0x00,0xff "1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" @@ -407,7 +410,7 @@ const uint8_t meter[]= "1,77070100000009ff@#," D_METERNR ",," DJ_METERNR ",0"; #endif -// Beispiel für einen OBIS Stromzähler und einen Gaszähler + Wasserzähler +// example OBIS power meter + gas and water counter #if METER==COMBO3b #undef METERS_USED #define METERS_USED 3 @@ -416,14 +419,14 @@ struct METER_DESC const meter_desc[METERS_USED]={ [1]={14,'c',0,50,"Gas"}, // GPIO14 gas counter [2]={1,'c',0,10,"Wasser"}}; // water counter -// 3 Zähler definiert +// 3 meters defined const uint8_t meter[]= "1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|" "1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|" "1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|" "1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|" -// bei gaszählern (countern) muss der Vergleichsstring so aussehen wie hier +// with counters the comparison string must be exactly this string "2,1-0:1.8.0*255(@100," D_GasIN ",cbm," DJ_COUNTER ",2|" "3,1-0:1.8.0*255(@100," D_H2oIN ",cbm," DJ_COUNTER ",2"; @@ -435,8 +438,8 @@ const uint8_t meter[]= #define METERS_USED 3 struct METER_DESC const meter_desc[METERS_USED]={ - [0]={1,'c',0,10,"H20",-1,1,0}, // GPIO1 Wasser Zähler - [1]={4,'c',0,50,"GAS",-1,1,0}, // GPIO4 gas Zähler + [0]={1,'c',0,10,"H20",-1,1,0}, // GPIO1 water counter + [1]={4,'c',0,50,"GAS",-1,1,0}, // GPIO4 gas counter [2]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}}; // SML harware serial RX pin const uint8_t meter[]= @@ -491,10 +494,12 @@ const uint8_t meter[]= // median filter eliminates outliers, but uses much RAM and CPU cycles // 672 bytes extra RAM with MAX_VARS = 16 -//#define USE_MEDIAN_FILTER +// default compile on, but must be enabled by descriptor flag 16 +// may be undefined if RAM must be saved +#define USE_SML_MEDIAN_FILTER // max number of vars , may be adjusted -#define MAX_VARS 16 +#define MAX_VARS 20 // max number of meters , may be adjusted #define MAX_METERS 5 double meter_vars[MAX_VARS]; @@ -506,6 +511,7 @@ uint8_t meters_used; struct METER_DESC const *meter_desc_p; const uint8_t *meter_p; +uint8_t meter_spos[MAX_METERS]; // software serial pointers TasmotaSerial *meter_ss[MAX_METERS]; @@ -520,24 +526,58 @@ char meter_id[MAX_METERS][METER_ID_SIZE]; #define EBUS_SYNC 0xaa #define EBUS_ESC 0xa9 -uint8_t ebus_pos; -uint8_t mbus_pos; -#ifdef USE_MEDIAN_FILTER +uint8_t sml_send_blocks; +uint8_t sml_100ms_cnt; +uint8_t sml_desc_cnt; + +#ifdef USE_SML_MEDIAN_FILTER // median filter, should be odd size #define MEDIAN_SIZE 5 -struct MEDIAN_FILTER { +struct SML_MEDIAN_FILTER { double buffer[MEDIAN_SIZE]; int8_t index; } sml_mf[MAX_VARS]; + +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; hcntbuffer[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; ocntavailable()) { + meter_ss[meters]->read(); + } +} + + void sml_shift_in(uint32_t meters,uint32_t shard) { uint32_t count; - if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m') { + if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m' && meter_desc_p[meters].type!='p') { // shift in for (count=0; count=9) { + smltbuf[meters][meter_spos[meters]] = iob; + meter_spos[meters]++; + if (meter_spos[meters]>=9) { SML_Decode(meters); - mbus_pos=0; + sml_empty_receiver(meters); + meter_spos[meters]=0; + } + } else if (meter_desc_p[meters].type=='p') { + smltbuf[meters][meter_spos[meters]] = iob; + meter_spos[meters]++; + if (meter_spos[meters]>=7) { + SML_Decode(meters); + sml_empty_receiver(meters); + meter_spos[meters]=0; } } else { if (iob==EBUS_SYNC) { // should be end of telegramm // QQ,ZZ,PB,SB,NN ..... CRC, ACK SYNC - if (ebus_pos>4+5) { + if (meter_spos[meters]>4+5) { // get telegramm lenght uint8_t tlen=smltbuf[meters][4]+5; // test crc if (smltbuf[meters][tlen]=ebus_CalculateCRC(smltbuf[meters],tlen)) { ebus_esc(smltbuf[meters],tlen); - //eBus_analyze(); - // XX0204UUSS@ SML_Decode(meters); - //AddLog_P(LOG_LEVEL_INFO, PSTR("ebus block found")); - //ebus_set_timeout(); } else { // crc error //AddLog_P(LOG_LEVEL_INFO, PSTR("ebus crc error")); } } - ebus_pos=0; + meter_spos[meters]=0; return; } - smltbuf[meters][ebus_pos] = iob; - ebus_pos++; - if (ebus_pos>=SML_BSIZ) { - ebus_pos=0; + smltbuf[meters][meter_spos[meters]] = iob; + meter_spos[meters]++; + if (meter_spos[meters]>=SML_BSIZ) { + meter_spos[meters]=0; } } sb_counter++; - if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m') SML_Decode(meters); + if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m' && meter_desc_p[meters].type!='p') SML_Decode(meters); } @@ -1356,14 +1407,22 @@ void SML_Decode(uint8_t index) { found=0; } } else { - // ebus mbus or raw + // ebus mbus pzem or raw // XXHHHHSSUU if (*mp=='x' && *(mp+1)=='x') { //ignore mp+=2; cp++; - } else if (*mp=='u' && *(mp+1)=='u' && *(mp+2)=='u' && *(mp+3)=='u'){ - uint16_t val = *cp|(*(cp+1)<<8); + } else if (!strncmp(mp,"uuuuuuuu",8)) { + uint32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0); + ebus_dval=val; + mbus_dval=val; + mp+=8; + cp+=4; + } + else if (*mp=='u' && *(mp+1)=='u' && *(mp+2)=='u' && *(mp+3)=='u'){ + uint16_t val = cp[0]|(cp[1]<<8); + mbus_dval=val; ebus_dval=val; mp+=4; cp+=2; @@ -1383,13 +1442,44 @@ void SML_Decode(uint8_t index) { ebus_dval=val; mp+=2; } - else if (*mp=='f' && *(mp+1)=='f' && *(mp+2)=='f' && *(mp+3)=='f' && *(mp+4)=='f' && *(mp+5)=='f' && *(mp+6)=='f' && *(mp+7)=='f') { - uint32_t val= (*(cp+0)<<24)|(*(cp+1)<<16)|(*(cp+2)<<8)|(*(cp+3)<<0); + 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 { uint8_t val = hexnibble(*mp++) << 4; val |= hexnibble(*mp++); @@ -1419,15 +1509,15 @@ void SML_Decode(uint8_t index) { } } else { double dval; - if (meter_desc_p[mindex].type!='e' && meter_desc_p[mindex].type!='r' && meter_desc_p[mindex].type!='m') { + if (meter_desc_p[mindex].type!='e' && meter_desc_p[mindex].type!='r' && meter_desc_p[mindex].type!='m' && meter_desc_p[mindex].type!='p') { // get numeric values if (meter_desc_p[mindex].type=='o' || meter_desc_p[mindex].type=='c') { - dval=xCharToDouble((char*)cp); + dval=CharToDouble((char*)cp); } else { dval=sml_getvalue(cp,mindex); } } else { - // ebus or mbus + // ebus pzem or mbus or raw if (*mp=='b') { mp++; uint8_t shift=*mp&7; @@ -1449,17 +1539,28 @@ void SML_Decode(uint8_t index) { //AddLog_P2(LOG_LEVEL_INFO, PSTR(">> %s"),mp); mp++; } else { - dval=ebus_dval; + if (meter_desc_p[mindex].type=='p') { + uint8_t crc = SML_PzemCrc(&smltbuf[mindex][0],6); + if (crc!=smltbuf[mindex][6]) goto nextsect; + dval=mbus_dval; + } else { + dval=ebus_dval; + } } } -#ifdef USE_MEDIAN_FILTER - meter_vars[vindex]=median(&sml_mf[vindex],dval); +#ifdef USE_SML_MEDIAN_FILTER + if (meter_desc_p[mindex].flag&16) { + meter_vars[vindex]=sml_median(&sml_mf[vindex],dval); + } else { + meter_vars[vindex]=dval; + } #else meter_vars[vindex]=dval; #endif +//AddLog_P2(LOG_LEVEL_INFO, PSTR(">> %s"),mp); // get scaling factor - double fac=xCharToDouble((char*)mp); + double fac=CharToDouble((char*)mp); meter_vars[vindex]/=fac; SML_Immediate_MQTT((const char*)mp,vindex,mindex); } @@ -1616,7 +1717,7 @@ void SML_Show(boolean json) { if (lastmind!=mindex) { // meter changed, close mqtt //snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s}", b_mqtt_data); - ResponseJsonEnd(); + ResponseAppend_P(PSTR("}")); // and open new //snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s,\"%s\":{\"%s\":%s", b_mqtt_data,meter_desc_p[mindex].prefix,jname,tpowstr); ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%s"),meter_desc_p[mindex].prefix,jname,tpowstr); @@ -1643,7 +1744,7 @@ void SML_Show(boolean json) { if (json) { //snprintf_P(b_mqtt_data, sizeof(b_mqtt_data), "%s}", b_mqtt_data); //ResponseAppend_P(PSTR("%s"),b_mqtt_data); - ResponseJsonEnd(); + ResponseAppend_P(PSTR("}")); } else { //WSContentSend_PD(PSTR("%s"),b_mqtt_data); } @@ -1733,10 +1834,15 @@ void SML_Init(void) { meter_desc_p=meter_desc; meter_p=meter; + sml_desc_cnt=0; + for (uint32_t cnt=0;cntM",-2,0); if (meter_script==99) { // use script definition if (script_meter) free(script_meter); + script_meter=0; uint8_t *tp=0; uint16_t index=0; uint8_t section=0; + uint8_t srcpin=0; char *lp=glob_script_mem.scriptptr; + sml_send_blocks=0; while (lp) { if (!section) { if (*lp=='>' && *(lp+1)=='M') { @@ -1770,7 +1879,9 @@ void SML_Init(void) { } if (mlen==0) return; // missing end # script_meter=(uint8_t*)calloc(mlen,1); - if (!script_meter) return; + if (!script_meter) { + goto dddef_exit; + } tp=script_meter; goto next_line; } @@ -1784,14 +1895,18 @@ void SML_Init(void) { // add descriptor +1,1,c,0,10,H20 //toLogEOL(">>",lp); lp++; - uint8_t index=*lp&7; + index=*lp&7; lp+=2; if (index<1 || index>meters_used) goto next_line; index--; - uint8_t srcpin=strtol(lp,&lp,10); + srcpin=strtol(lp,&lp,10); if (Gpio_used(srcpin)) { - AddLog_P(LOG_LEVEL_INFO, PSTR("gpio double define!")); - return; + AddLog_P(LOG_LEVEL_INFO, PSTR("gpio rx double define!")); +dddef_exit: + if (script_meter) free(script_meter); + script_meter=0; + meters_used=METERS_USED; + goto init10; } script_meter_desc[index].srcpin=srcpin; if (*lp!=',') goto next_line; @@ -1815,6 +1930,10 @@ void SML_Init(void) { if (*lp==',') { lp++; script_meter_desc[index].trxpin=strtol(lp,&lp,10); + if (Gpio_used(script_meter_desc[index].trxpin)) { + AddLog_P(LOG_LEVEL_INFO, PSTR("gpio tx double define!")); + goto dddef_exit; + } if (*lp!=',') goto next_line; lp++; script_meter_desc[index].tsecs=strtol(lp,&lp,10); @@ -1838,6 +1957,7 @@ void SML_Init(void) { } script_meter_desc[index].index=0; script_meter_desc[index].max_index=tx_entries; + sml_send_blocks++; } } } @@ -1879,6 +1999,7 @@ next_line: } #endif +init10: typedef void (*function)(); function counter_callbacks[] = {SML_CounterUpd1,SML_CounterUpd2,SML_CounterUpd3,SML_CounterUpd4}; uint8_t cindex=0; @@ -1916,7 +2037,7 @@ next_line: } else { // serial input, init #ifdef SPECIAL_SS - if (meter_desc_p[meters].type=='m') { + if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='p') { meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1); } else { meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,1); @@ -2028,26 +2149,38 @@ char *SML_Get_Sequence(char *cp,uint32_t index) { } } -uint8_t sml_250ms_cnt; - - void SML_Check_Send(void) { - sml_250ms_cnt++; - for (uint32_t cnt=0; cnt=0 && script_meter_desc[cnt].txmem) { - if ((sml_250ms_cnt%script_meter_desc[cnt].tsecs)==0) { + if ((sml_100ms_cnt%script_meter_desc[cnt].tsecs)==0) { if (script_meter_desc[cnt].max_index>1) { script_meter_desc[cnt].index++; if (script_meter_desc[cnt].index>=script_meter_desc[cnt].max_index) { script_meter_desc[cnt].index=0; + sml_desc_cnt++; } - char *cp=SML_Get_Sequence(script_meter_desc[cnt].txmem,script_meter_desc[cnt].index); - SML_Send_Seq(cnt,cp); - //AddLog_P2(LOG_LEVEL_INFO, PSTR(">> %s"),cp); + cp=SML_Get_Sequence(script_meter_desc[cnt].txmem,script_meter_desc[cnt].index); + //SML_Send_Seq(cnt,cp); } else { - SML_Send_Seq(cnt,script_meter_desc[cnt].txmem); + cp=script_meter_desc[cnt].txmem; + //SML_Send_Seq(cnt,cp); + sml_desc_cnt++; } + //AddLog_P2(LOG_LEVEL_INFO, PSTR(">> %s"),cp); + SML_Send_Seq(cnt,cp); + if (sml_desc_cnt>=meters_used) { + sml_desc_cnt=0; + } + break; } + } else { + sml_desc_cnt++; + } + + if (sml_desc_cnt>=meters_used) { + sml_desc_cnt=0; } } } @@ -2066,7 +2199,7 @@ uint8_t sml_hexnibble(char chr) { // send sequence every N Seconds void SML_Send_Seq(uint32_t meter,char *seq) { uint8_t sbuff[32]; - uint8_t *ucp=sbuff,slen; + uint8_t *ucp=sbuff,slen=0; char *cp=seq; while (*cp) { if (!*cp || !*(cp+1)) break; @@ -2086,6 +2219,15 @@ void SML_Send_Seq(uint32_t meter,char *seq) { *ucp++=highByte(crc); slen+=4; } + if (script_meter_desc[meter].type=='p') { + *ucp++=0xc0; + *ucp++=0xa8; + *ucp++=1; + *ucp++=1; + *ucp++=0; + *ucp++=SML_PzemCrc(sbuff,6); + slen+=6; + } meter_ss[meter]->write(sbuff,slen); } #endif // USE_SCRIPT @@ -2107,6 +2249,11 @@ uint16_t MBUS_calculateCRC(uint8_t *frame, uint8_t num) { 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) { @@ -2200,7 +2347,7 @@ bool Xsns53(byte function) { else SML_Poll(); break; #ifdef USE_SCRIPT - case FUNC_EVERY_250_MSECOND: + case FUNC_EVERY_100_MSECOND: SML_Check_Send(); break; #endif // USE_SCRIPT