/** * ZPA-ZE311 VDE-DIN decode StromLog * Version 5.01 * 20.11.2022 P. Rebesky * author Creator P.Rebesky * Copyright (©): 2022-2024 by Peter Rebesky * This code can use in private cases only. Every business or companies using of this codes or codes parts is required an approval of us (me) * Every private using can exchange some parts of code or modify some code-lines. This code is allowed change for private use only. * This software is basicly owned by Peter Rebesky and any comercial using is forbidden without approval of us (me). **/ #ifndef DECODE_METER_H_ #define DECODE_METER_H_ //#define VALUE_SERIAL_DEBUG //#define UNIT_TEST // debug for test by delivery data from pc into serial 7E1 9600 baud #include "global.h" char SetBaud19200[] = {0x06,0x30,0x36,0x30,0x0d,0x0a,0x00}; // 6 char SetBaud9600[] = {0x06,0x30,0x35,0x30,0x0d,0x0a,0x00}; // 5 char SetBaud4800[] = {0x06,0x30,0x34,0x30,0x0d,0x0a,0x00}; // 4 char SetBaud2400[] = {0x06,0x30,0x33,0x30,0x0d,0x0a,0x00}; // 3 char SetBaud1200[] = {0x06,0x30,0x32,0x30,0x0d,0x0a,0x00}; // 2 extern bool send2dbasePOST(String content, bool saveIfError); extern String sendProtokollDebugDIN(String protData); extern int getWandlerFactor(); /***** class for data of meter *****************************/ class meterData{ public: uint32_t MeterID[3]; //[_MID0][_MID1][_MID2] uint32_t MeterKWH[4]; //[_FROMGRID_0][_FROMGRID_1][_FROMGRID_2][_FEEDIN] int Electric_data[11]; //[_U_L1][_U_L2][_U_L3][_I_L1][_I_L2][_I_L3][_P_L1][_P_L2][_P_L3][_CONSUMPTION][_FREQ] String getMeterID(); String getMeter_ID(); private: }; String meterData::getMeterID(){ String ret; char dummy[8]; sprintf(dummy,"%08X",MeterID[_MID2]); ret += dummy; sprintf(dummy,"%08X",MeterID[_MID1]); ret += dummy; sprintf(dummy,"%08X",MeterID[_MID0]); ret += dummy; return ret; } String meterData::getMeter_ID(){ String ret; char dummy[8]; sprintf(dummy,"%X",MeterID[_MID2]); ret += dummy; ret +="-"; sprintf(dummy,"%08X",MeterID[_MID1]); ret += dummy; ret +="-"; sprintf(dummy,"%08X",MeterID[_MID0]); ret += dummy; return ret; } /***** end of meterData ************************************/ /***** class for decode data from meter - SML protocol *****/ class decodeMeter : public meterData { private: int PointerEOT; int BufferLength; int ValueScale; int SingleValue; int SerialSpeed; uint32_t value0; uint32_t value1; uint32_t value2; uint16_t CRC_ccitt; uint16_t CRC_meter; bool Bezug1OK; bool Bezug2OK; bool Bezug3OK; bool FeedInOK; char MeterManu[6]; char ExtractValue[14]; int SearchOBIS(char *searchCode, int slength); int CheckOBIS(char *checkCode, int i, int searchLength); void sendSerialSpeed(int speedIndicator); uint32_t getOneValueDIN(int actPointer, int length, int valueType, int unitScale); public: uint32_t ErrorCounter=0; uint32_t ProtocolCounter=0; int SendProtocols=0; int Prot_Type; String MeterDate; String MeterTime; String MeterType = NOT_DETECT; String Buffer4Send; String DecodeOneValueDIN(int actPointer, int length, int type); void Begin(int maxBuffer); String getProtocolType(); int mainEntryDecode(); int detectMeter(int pointer); int decodeDataDIN(int pointer); void clearValues(); int getErrorsPercent(); void sendRequest2Meter(); void crc_add(uint16_t dummy); // it is just for compatiblity; meter doesnt delivered crc check byte }; void decodeMeter::Begin(int maxBuffer){ PointerEOT=maxBuffer; BufferLength=maxBuffer; CRC_ccitt = 0xffff; CRC_meter = 0xffff; Prot_Type = _DETECT_prot; SerialSpeed=0; } String decodeMeter::getProtocolType(){ // it's in user for build-versions-info return "ZE311 "; } int decodeMeter::getErrorsPercent(){ if(ProtocolCounter > 0){ return ErrorCounter*100/ProtocolCounter; } else return 0; } void decodeMeter::clearValues(){ for (int i=0;i<10;i++){ Electric_data[i]=0; } } //*** end *** //*** only for compatiblity *** void decodeMeter::crc_add(uint16_t dummy){ } //****************************** search OBIS-code signation ***************************** int decodeMeter::SearchOBIS(char *searchCode, int slength){ int x = 0; do { if (*searchCode == _dataDTZ[x]){ if (CheckOBIS(searchCode, x, slength)==0)break; } x++; } while (x < BufferLength-1); if(x > BufferLength - 10)return 0; // nothing of OBIS founded else return x+slength+1; // pointer on the first value after OBIS-String } //****************************** compare search-string with buffer ***************************** int decodeMeter::CheckOBIS(char *checkCode, int i, int searchLength){ int result = 0; for (int y=0;y<=searchLength;y++){ if(*(checkCode+y) != _dataDTZ[i+y]) return -1; // check chars if identical } return result; } //*********************** send answer to meter of serial speed ****** void decodeMeter::sendSerialSpeed(int speedIndicator){ switch (speedIndicator){ case 1: break; case 2: Serial.print(SetBaud1200);delay(400); Serial.begin(1200,SERIAL_7E1); break; case 3: Serial.print(SetBaud2400);delay(350); Serial.begin(2400,SERIAL_7E1); break; case 4: Serial.print(SetBaud4800);delay(300); Serial.begin(4800,SERIAL_7E1); break; case 5: Serial.print(SetBaud9600);delay(200); Serial.begin(9600,SERIAL_7E1); break; case 6: Serial.print(SetBaud19200);delay(200); Serial.begin(9600,SERIAL_7E1); break; default: break; } } //************** decode one value from data ********************************************/ uint32_t decodeMeter::getOneValueDIN(int actPointer, int length, int valueType, int unitScale){ //uint32_t getOneValueSML(int startpoint, int endpoint, int valueType, int target-scaler) int32_t result= -1; if(_dataDTZ[actPointer] == 0x28){ // if begin by "(" -> then OK if(valueType == 0){ // meternumber String meterNumber = ""; for(int i=1; i < length; i++){ if(_dataDTZ[actPointer+i] == 0x29) break; //length = 0; // break if appear else meterNumber += _dataDTZ[actPointer+i]; } int numberLength = meterNumber.length(); if (numberLength <= 10) MeterID[_MID0] = meterNumber.toInt(); else { length = numberLength-7; MeterID[_MID0] = meterNumber.substring(length-1).toInt(); valueType = 1; } // Serial.println(MeterID[_MID0]); // debug result=MeterID[_MID0]; } if(valueType == 1){ //meter manufracture int oneChar = 0; MeterID[_MID2] = 0x000000A0; for(int i=1; i < length; i++){ oneChar = _dataDTZ[actPointer+i]; if(oneChar == 0x29) break; //length = 1; // break if appear if(oneChar >= 0x30 && oneChar <= 0x39){ oneChar &= 0x0f; if(i<4 && oneChar!=0){ MeterID[_MID2] = MeterID[_MID2] << 4; MeterID[_MID2] += oneChar; } MeterID[_MID1] = MeterID[_MID1] << 4; MeterID[_MID1] += oneChar; } else { MeterID[_MID1] = MeterID[_MID1] << 8; MeterID[_MID1] += oneChar; } } result=0; } if(valueType == 0x50){ String valueSing = "0"; int i; int dot=0; bool negative = false; for(i=1; i < length; i++){ if(_dataDTZ[actPointer+i] == 0x2a) length = 1; // break if appear * if(dot != 0 && i-dot > 4){length = 1; i++; break;} // break if numbers after dot larger then 4, because just 32 bit value if(_dataDTZ[actPointer+i] == 0x2e) dot=i; if(_dataDTZ[actPointer+i] == 0x2d) negative = true; // check if negativ value if(_dataDTZ[actPointer+i] >= 0x30 && _dataDTZ[actPointer+i] <= 0x39) valueSing += _dataDTZ[actPointer+i]; } result = valueSing.toInt(); if(negative) result *= -1; int dotx=i-dot-2; if (dot > 0){ switch (dotx){ case 1: if(unitScale>=10) result *= (unitScale/10); else result /= (unitScale*10); break; case 2: if(unitScale>=100) result *= (unitScale/100); else result /= (unitScale*100); break; case 3: if(unitScale>=1000) result *= (unitScale/1000); else result /= (unitScale*1000); break; case 4: if(unitScale>=10000) result *= (unitScale/10000); else result /= (unitScale*10000); break; default: result *= unitScale; break; } } else result *= unitScale; // default value } if(valueType == 0x10){ MeterTime = ""; int c=1; for(int i=1; i < length; i++){ if(_dataDTZ[actPointer+i] == 0x29) break; // break if appeared ')' if(_dataDTZ[actPointer+i]>= 0x30 && _dataDTZ[actPointer+i]<= 0x39){ //let out '-' and ':' MeterTime += _dataDTZ[actPointer+i]; if(c == 2 || c == 4) MeterTime += ":"; c++; } } } if(valueType == 0x11){ MeterDate = ""; int c=1; for(int i=1; i < length; i++){ if(_dataDTZ[actPointer+i] == 0x29) break; // break if appeared ')' if(_dataDTZ[actPointer+i]>= 0x30 && _dataDTZ[actPointer+i]<= 0x39){ //let out '-' and ':' MeterDate += _dataDTZ[actPointer+i]; if(c == 2 || c == 4) MeterDate += "-"; c++; } } } } return result; } //*** end *** //******************automatic detect meter-type ************************** int decodeMeter::detectMeter(int pointer){ if (_dataDTZ[pointer] == 0x0a && _dataDTZ[pointer-1] == 0x0d){ // check of end-code ") + cr + lf" int poi=SearchOBIS("/",0); if(poi>0){ MeterType=""; if (_dataDTZ[poi] != 0x3f && _dataDTZ[poi+1] != 0x3f){ SerialSpeed = _dataDTZ[poi+3] & 0x0f; if (SerialSpeed > 0 && SerialSpeed < 7){ for(int i=0;i0x2f && _dataDTZ[poi+i]<0x5b && _dataDTZ[poi+i]!=0x40) {MeterType += _dataDTZ[poi+i];} // eleminate all control characters if(i<5) {MeterManu[i] = _dataDTZ[poi+i];} } MeterManu[5] = 0; delay(200); sendSerialSpeed(SerialSpeed); } } } pointer = 0; _dataDTZ[0] = 0; } if (_dataDTZ[pointer-2] == 0x21){pointer = 0;} pointer++; return pointer; } //**** main entry for find meter-type and selcet protocol-type ******* int decodeMeter::mainEntryDecode(){ if(SerialSpeed != 0) _dataPointer=decodeDataDIN(_dataPointer); else _dataPointer=detectMeter(_dataPointer); return 0; } //**** send a request to meter -> only for old types like AS1440 or else types ****/ void decodeMeter::sendRequest2Meter(){ #ifdef UNIT_TEST Serial.begin(9600,SERIAL_7E1); // 9600 baud 7-E-1 // debug #else Serial.begin(300,SERIAL_7E1); // 300 baud 7-E-1 #endif if (MeterType[0]==69 && MeterType[1]==76 && MeterType[2]==83 ) Serial.println("/2!"); // send get short data with V/A from AS1440 (ELS) else Serial.println("/?!"); // send request to meter for getting data } //*********************** decode data from Logarex LX13BE ************ int decodeMeter::decodeDataDIN(int pointer){ if (_dataDTZ[pointer] == 0x0a && _dataDTZ[pointer-1] == 0x0d){ // check of end-code ") + cr + lf" int32_t result= -1; if (SearchOBIS(MeterManu,4)> 0){ #ifdef UNIT_TEST Serial.print(SetBaud9600); // sent meter for comunication with baud 9600 #else delay(200); sendSerialSpeed(SerialSpeed); #endif _ShowProtocolTime = 400; } result=getOneValueDIN(SearchOBIS("0.0.0",4),17,0,1); // Zählernummer and if(result==-1)getOneValueDIN(SearchOBIS("C.1.0",4),17,0,1); result=getOneValueDIN(SearchOBIS("0.0.1",4),9,1,1); // and manufracture getOneValueDIN(SearchOBIS("0.9.1",4),10,0x10,1); // get meter-time getOneValueDIN(SearchOBIS("0.9.2",4),10,0x11,1); // get meter-date result = getOneValueDIN(SearchOBIS("1.8.0",4),12,0x50,1000); // Bezug fromGridS if(result > 0 && Bezug1OK == false){MeterKWH[_FROMGRID_0]=result*getWandlerFactor(); Bezug1OK = true;} result = getOneValueDIN(SearchOBIS("1.8.1",4),12,0x50,1000); // Bezug fromGridA if(result > 0 && Bezug2OK == false){MeterKWH[_FROMGRID_1]=result*getWandlerFactor(); Bezug2OK = true;} result = getOneValueDIN(SearchOBIS("1.8.2",4),12,0x50,1000); // Bezug fromGridA if(result > 0 && Bezug3OK == false){MeterKWH[_FROMGRID_2]=result*getWandlerFactor(); Bezug3OK = true;} result = getOneValueDIN(SearchOBIS("2.8.0",4),12,0x50,1000); // EEG intoGrid if(result > 0 && FeedInOK == false){MeterKWH[_FEEDIN]=result*getWandlerFactor(); FeedInOK = true;} if(SearchOBIS("*kW",2)>0){ // is neseccary to identify consumtion! result = getOneValueDIN(SearchOBIS("16.7.0",5),10,0x50,1000); // P in W if(result != -1)Electric_data[_CONSUMPTION]=result*getWandlerFactor(); } if(SearchOBIS("*V",1)>0){ // is neseccary to identify result = getOneValueDIN(SearchOBIS("32.7.0",5),10,0x50,10); if(result != -1)Electric_data[_U_L1]=result; result = getOneValueDIN(SearchOBIS("52.7.0",5),10,0x50,10); if(result != -1)Electric_data[_U_L2]=result; result = getOneValueDIN(SearchOBIS("72.7.0",5),10,0x50,10); if(result != -1)Electric_data[_U_L3]=result; } if(SearchOBIS("*A",1)>0){ // is neseccary to identify result = getOneValueDIN(SearchOBIS("31.7.0",5),10,0x50,1000); if(result != -1)Electric_data[_I_L1]=result*getWandlerFactor()/10; result = getOneValueDIN(SearchOBIS("51.7.0",5),10,0x50,1000); if(result != -1)Electric_data[_I_L2]=result*getWandlerFactor()/10; result = getOneValueDIN(SearchOBIS("71.7.0",5),10,0x50,1000); if(result != -1)Electric_data[_I_L3]=result*getWandlerFactor()/10; } if (_dataDTZ[pointer-2] == 0x21){ Bezug1OK = false; Bezug2OK = false; Bezug3OK = false; FeedInOK = false; ProtocolCounter ++; // count the protocols //************** protokoll debug only ****************/ if(SendProtocols > 0){ send2dbasePOST(sendProtokollDebugDIN(Buffer4Send),false); //debug SendProtocols --; Buffer4Send =""; } } if(SendProtocols > 0){ for(int i=1;i