sml, median filter, mutiple modbus, pzem

This commit is contained in:
gemu2015 2019-09-16 19:58:22 +02:00
parent 7d768905cd
commit 37f6fc6bac
1 changed files with 219 additions and 72 deletions

View File

@ -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 <TasmotaSerial.h>
// 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; 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 median(struct MEDIAN_FILTER* mf, double in) {
double tbuff[MEDIAN_SIZE],tmp;
uint8_t flag;
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++) {
@ -553,6 +593,7 @@ double median(struct MEDIAN_FILTER* mf, double in) {
if (!flag) break;
}
return tbuff[MEDIAN_SIZE/2];
*/
}
#endif
@ -714,8 +755,7 @@ uint16_t ADS1115::read_register(uint8_t reg)
uint8_t result = Wire.requestFrom((int)m_address, 2, 1);
if (result != 2) {
Wire.flush();
return 0;
return 0;
}
uint16_t val;
@ -1056,9 +1096,8 @@ uint8_t hexnibble(char chr) {
uint8_t sb_counter;
// because orig CharToDouble was defective
// fixed in Tasmota 6.4.1.19 20190222
double xCharToDouble(const char *str)
// 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];
@ -1145,9 +1184,16 @@ uint8_t ebus_CalculateCRC( uint8_t *Data, uint16_t DataLen ) {
return Crc;
}
void sml_empty_receiver(uint32_t meters) {
while (meter_ss[meters]->available()) {
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<SML_BSIZ-1; count++) {
smltbuf[meters][count]=smltbuf[meters][count+1];
@ -1162,43 +1208,48 @@ void sml_shift_in(uint32_t meters,uint32_t shard) {
} else if (meter_desc_p[meters].type=='r') {
smltbuf[meters][SML_BSIZ-1]=iob;
} else if (meter_desc_p[meters].type=='m') {
smltbuf[meters][mbus_pos] = iob;
mbus_pos++;
if (mbus_pos>=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;cnt<MAX_VARS;cnt++) {
meter_vars[cnt]=0;
}
for (uint32_t cnt=0;cnt<MAX_METERS;cnt++) {
meter_spos[cnt]=0;
}
#ifdef USE_SCRIPT
@ -1744,17 +1850,20 @@ void SML_Init(void) {
if (script_meter_desc[cnt].txmem) {
free(script_meter_desc[cnt].txmem);
script_meter_desc[cnt].txmem=0;
}
}
}
uint8_t meter_script=Run_Scripter(">M",-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<meters_used; cnt++) {
sml_100ms_cnt++;
char *cp;
for (uint32_t cnt=sml_desc_cnt; cnt<meters_used; cnt++) {
if (script_meter_desc[cnt].trxpin>=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