/** * SML 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 #include "global.h" extern bool send2dbasePOST(String content, bool saveIfError); extern String sendProtokollDebug(); /***** 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 meterManu; 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; uint32_t value0; uint32_t value1; uint32_t value2; uint16_t CRC_ccitt; uint16_t CRC_meter; char ExtractValue[14]; uint16_t refUint(uint16_t reflector, int bits); void readValuesOBIS(); int SearchOBIS(char *searchCode, int slength); int CheckOBIS(char *checkCode, int i, int searchLength); int dropValues(int actPointer, int drops); int getScalerActValue(int actPointer); int DecodeOneValue(int actPointer, int drops); uint32_t getOneValueDIN(int startpoint, int endpoint, int valueType, int unitScale); public: uint32_t ErrorCounter=0; uint32_t ProtocolCounter=0; int SendProtocols=0; int Prot_Type; String MeterType = NOT_DETECT; void Begin(int maxBuffer); String getProtocolType(); int mainEntryDecode(); int detectMeter(int pointer); int decodeData(int pointer); int decodeDataDIN(int pointer); void clearValues(); int getErrorsPercent(); void sendRequest2Meter(); void crc_add(uint16_t oneByte); uint16_t crc_one_byte(uint16_t crcR, uint16_t oneByte, uint16_t poly); }; void decodeMeter::Begin(int maxBuffer){ PointerEOT=maxBuffer; BufferLength=maxBuffer; CRC_ccitt = 0xffff; CRC_meter = 0xffff; Prot_Type = _DETECT_prot; } String decodeMeter::getProtocolType(){ // it's in user for build-versions-info return "SML"; } 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; } } //**** collect and decode data after get EOT **********************// int decodeMeter::decodeData(int pointer){ if (pointer > BufferLength / 4) { if (_dataDTZ[pointer-1] == 0x1a && _dataDTZ[pointer-2] == 0x1b){ // check of end-code add fill-byte-check if(_dataDTZ[pointer-3] == 0x1b && _dataDTZ[pointer-4] == 0x1b){ // really is end-code PointerEOT = pointer; // save pointer of data-buffer-end // ProtocolCounter ++; // count the protocols _ShowProtocolTime = 200; } } if(PointerEOT == pointer) { CRC_ccitt = refUint(CRC_meter,16)^0xffff; // reflected CRC-output and finish it with xor by 0xffff } } pointer ++; if(pointer > PointerEOT+2) { // protcol end and now arriveds the CRC-bytes only uint16_t check_crc = _dataDTZ[PointerEOT+2]; // load the first value of crc check_crc <<= 8; // do it to a MSB check_crc |= _dataDTZ[PointerEOT+1]; // add the lower part //**************** debug crc check ********************************** #ifdef crc16check Serial.print(check_crc); // debug Serial.print(":"); // debug Serial.println(CRC_ccitt); // debug #endif //**************** debug end **************************************** if(check_crc == CRC_ccitt){ // compare crc-strings readValuesOBIS(); } else { ErrorCounter ++; } //************** protokoll debug only ****************/ if(SendProtocols > 0){ send2dbasePOST(sendProtokollDebug(),false); //debug SendProtocols --; } //************** protokoll debug end *****************/ PointerEOT = BufferLength-2; ProtocolCounter ++; // count the protocols pointer = 0; // clear for new begin of protocol CRC_meter = 0xFFFF; // set crc to begin by 0xffff } return pointer; } //*** end *** //************ reflected uint16_t for CRC calculation ******************** // this function turns the bits -> MSB will be LSB from 0111001 to 1001110 // it's needed for counting crc reflected input and reflected output uint16_t decodeMeter::refUint(uint16_t reflector, int bits){ // bits = how many bits should be reflected uint16_t shift=0; int i=0; do { shift <<= 1; if(reflector & 0x0001) shift |= 1; reflector >>= 1; i++; } while (i < bits); return shift; } //******************* calculate CRC16-CCITT ****************************** uint16_t decodeMeter::crc_one_byte(uint16_t crcR, uint16_t oneByte, uint16_t poly){ oneByte = refUint(oneByte,8); // reflected input oneByte <<= 8; // shift left eight times for(int i=0; i<8; i++){ if((crcR ^ oneByte) & 0x8000) crcR =(crcR << 1)^ poly; else crcR <<= 1; oneByte <<= 1; } return crcR; } void decodeMeter::crc_add(uint16_t oneByte){ CRC_meter = crc_one_byte(CRC_meter, oneByte, _POLYNOM); } //*** end *** //****************** read and decode values according OBIS-code ******************* void decodeMeter::readValuesOBIS(){ int scaler=1; DecodeOneValue(SearchOBIS(OBISoutGridS,7),4); //7 bytes check and drop 4 values until target if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; MeterKWH[_FROMGRID_0] = SingleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBISoutGridA,7),4); // the return-value ValueScale contents potency *10^x / SingleValue = value of OBIS if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; MeterKWH[_FROMGRID_1] = SingleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBISoutGridB,7),4); // the return-value ValueScale contents potency *10^x / SingleValue = value of OBIS if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; MeterKWH[_FROMGRID_2] = SingleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBISintoGrid,7),4); // the return-value ValueScale contents potency *10^x / SingleValue = value of OBIS if(ValueScale < 0) scaler = ValueScale * -10; else scaler = ValueScale / 10; MeterKWH[_FEEDIN] = SingleValue*scaler; scaler=1; DecodeOneValue(SearchOBIS(OBIS_Watt,7),4); if (SingleValue == 0){ DecodeOneValue(SearchOBIS(OBIS_WattA,7),4); // the return-value ValueScale contents potency *10^x / SingleValue = value of OBIS if(SingleValue ==0){ DecodeOneValue(SearchOBIS(OBIS_WattD,7),4); // SingleValue /= 10; // Serial.println(SingleValue); //debug // Serial.println(ValueScale); //debug } } Electric_data[_CONSUMPTION] = SingleValue / ValueScale; DecodeOneValue(SearchOBIS(OBIS_U_L1,7),4); Electric_data[_U_L1] = SingleValue; // the return-value ValueScale contents potency *10*x / SingleValue = value of OBIS DecodeOneValue(SearchOBIS(OBIS_U_L2,7),4); Electric_data[_U_L2] = SingleValue; DecodeOneValue(SearchOBIS(OBIS_U_L3,7),4); Electric_data[_U_L3] = SingleValue; DecodeOneValue(SearchOBIS(OBIS_I_L1,7),4); Electric_data[_I_L1] = SingleValue; DecodeOneValue(SearchOBIS(OBIS_I_L2,7),4); Electric_data[_I_L2] = SingleValue; DecodeOneValue(SearchOBIS(OBIS_I_L3,7),4); Electric_data[_I_L3] = SingleValue; DecodeOneValue(SearchOBIS(OBIS_P_L1,7),4); Electric_data[_P_L1] = SingleValue / ValueScale; DecodeOneValue(SearchOBIS(OBIS_P_L2,7),4); Electric_data[_P_L2] = SingleValue / ValueScale; DecodeOneValue(SearchOBIS(OBIS_P_L3,7),4); Electric_data[_P_L3] = SingleValue / ValueScale; DecodeOneValue(SearchOBIS(OBIS_Freq,7),4); Electric_data[_FREQ] = SingleValue; DecodeOneValue(SearchOBIS(OBIS_DTZ_ID,7),4); MeterID[_MID0] = value0; MeterID[_MID1] = value1; MeterID[_MID2] = value2; //[_MID0][_MID1][_MID2] if (MeterID[_MID0] == 0){ DecodeOneValue(SearchOBIS(OBIS_ServerID,7),4); MeterID[_MID0] = value0; MeterID[_MID1] = value1; MeterID[_MID2] = value2; } DecodeOneValue(SearchOBIS(OBIS_Manuf,7),4); if (value0 !=0 ){ MeterType = ""; int i3= value0 & 0x00ff; int i2 = (value0 >>8) & 0x00ff; int i1 = (value0 >>16) & 0x00ff; MeterType += (char)i1; MeterType += (char)i2; MeterType += (char)i3; } } //****************************** 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; } //****************** drop values until target value ******************* int decodeMeter::dropValues(int actPointer, int drops){ int d=0; int c=0; if(drops > 0){ for(int i=0; i 10) break; d=_dataDTZ[actPointer]; // get value type and length c=d&0x70; if(c == 0x70){ // jump over list-elements too actPointer++; drops += d&0x0f; // Serial.println(drops); //debug } else { d=d&0x0f; // extract lenght actPointer += d; // add length to drop value } } } return actPointer; } //************** get scale-factor for value *********************************************/ int decodeMeter::getScalerActValue(int actPointer){ int actual_sc = _dataDTZ[actPointer]; int scaler = 1; if(actual_sc == 0x52){ scaler = _dataDTZ[actPointer+1]; switch (scaler){ case 251: ValueScale=100; break; //0xFB case 252: ValueScale=100; break; //0xFC case 253: ValueScale=1000; break; //0xFD case 254: ValueScale=100; break; //0xFE case 255: ValueScale=10; break; //0xFF case 0: ValueScale=1; break; case 1: ValueScale=-10; break; case 2: ValueScale=-100; break; case 3: ValueScale=-1000; break; default: ValueScale=1; break; } } return scaler; } //************** decode one value from data ********************************************/ int decodeMeter::DecodeOneValue(int actPointer, int drops){ value0=value1=value2=0; uint32_t valueD=0; // output, set zero before reading SingleValue = 0; float value=0; int scaler=0; if(actPointer == 0) return 0; actPointer = dropValues(actPointer,drops); if (_dataDTZ[actPointer] == 01) return actPointer+1; // check if data not in used if (_dataDTZ[actPointer] == 00) return actPointer; // check if data-block is end if (_dataDTZ[actPointer] == 77) return actPointer; // check if new data-block begin scaler = getScalerActValue(actPointer-2); // get scaler of actual value char actual_TL = _dataDTZ[actPointer]; int actual_Type = actual_TL & 0xf0; int actual_Length = actual_TL & 0x0f; byte checkNegative = _dataDTZ[actPointer+1]; if(actual_Length == 9 && actual_Type == 0x50 && bitRead(checkNegative, 7) ){ // get only the last 4 bytes if eigth bytes delivered as integer actual_Length -= 4; actPointer +=4; } for (int i=1; i 5){ // for numbers-length greater than 32 bits value *= 256; value += _dataDTZ[actPointer+i]; } else{ // for numbers-length until 32 bits SingleValue = SingleValue << 8; SingleValue += _dataDTZ[actPointer+i]; } } if (actual_Type == 0x00){ // get strings by max 12 bytes big if (i >=8){ value2 = value2 << 8; valueD = value1; valueD = valueD >> 24; value2 += valueD; } if (i >= 4){ value1 = value1 << 8; valueD = value0; valueD = valueD >> 24; value1 += valueD; } value0 = value0 << 8; value0 += _dataDTZ[actPointer+i]; } ExtractValue[i-1] = _dataDTZ[actPointer+i]; ExtractValue[i]=0; } if(actual_Type == 0x50 && actual_Length < 5){ switch(actual_Length){ // handle negative values and set the rest of int32 case 4: if(bitRead(SingleValue,13)){ SingleValue |= 0xff000000;} break; case 3: if(bitRead(SingleValue,15)){ SingleValue |= 0xffff0000;} break; case 2: if(bitRead(SingleValue, 7)){ SingleValue |= 0xffffff00;} break; default: break; } } if((actual_Type==0x60 || actual_Type==0x50) && actual_Length >5){ // only for length > 4 bytes switch(scaler){ case 251: SingleValue = value /100000; break; case 252: SingleValue = value /10000; break; default: SingleValue = value; break; } } #ifdef VALUE_SERIAL_DEBUG Serial.print("BigValue: "); Serial.println(value); // debug Serial.print("SingValue: "); Serial.println(SingleValue); // debug Serial.print("Scaler: "); Serial.println(scaler); // debug #endif return actPointer+actual_Length; } //******************automatic detect meter-type ************************** int decodeMeter::detectMeter(int pointer){ if (_dataDTZ[pointer] == 0x0a && _dataDTZ[pointer-1] == 0x0d){ // check of end-code int poi=SearchOBIS("/",0); if (poi > 0){ MeterType =""; int serialSpeed = _dataDTZ[poi+3] & 0x0f; if (serialSpeed > 4 && serialSpeed < 9){ for(int i=poi; iBufferLength-10) break; } Prot_Type = _DIN_prot; pointer = 0; // clear for new begin of protocol } } } if (_dataDTZ[pointer] == 0x1a && _dataDTZ[pointer-1] == 0x1b){ // check of end-code add fill-byte-check if(_dataDTZ[pointer-2] == 0x1b && _dataDTZ[pointer-3] == 0x1b){ // really is end-code if (SearchOBIS(ProtBegin,4) !=0 ){ // check if protocol is complete if (SearchOBIS("HLY",2) !=0 ){ Prot_Type = _SML_prot; MeterType = "HLY"; } else if (SearchOBIS("ISK",2) !=0 ){ Prot_Type = _SML_prot; MeterType = "ISK"; } else if (SearchOBIS("EMH",2) !=0 ){ Prot_Type = _SML_prot; MeterType = "EMH"; } else if (SearchOBIS("EBZ",2) !=0 ){ Prot_Type = _SML_prot; MeterType = "EBZ"; } else if (SearchOBIS("ESY",2) !=0 ){ Prot_Type = _SML_prot; MeterType = "ESY"; } else { Prot_Type = _SML_prot; MeterType = GENERETIC; } } pointer = 0; // clear for new begin of protocol } } pointer ++; return pointer; } //**** main entry for find meter-type and selcet protocol-type ******* int decodeMeter::mainEntryDecode(){ switch (this->Prot_Type){ case _DETECT_prot: _dataPointer=detectMeter(_dataPointer); break; case _SML_prot: _dataPointer=decodeData(_dataPointer); break; case _DIN_prot: _dataPointer=decodeDataDIN(_dataPointer); break; default: _dataPointer=detectMeter(_dataPointer); break; } return 0; } //**** send a request to meter -> only for old types like AS1440 or else types ****/ void decodeMeter::sendRequest2Meter(){ // Serial.println("/?!"); // is not required for SML or new meter-types -> also do nothing } //*********************** decode one value of s0 protocol ************ uint32_t decodeMeter::getOneValueDIN(int startpoint, int endpoint, int valueType, int unitScale){ int32_t result= 0; if(_dataDTZ[startpoint] == 0x28){ // if begin by "(" -> then OK if(valueType == 0){ // meternumber String meterNumber = ""; for(int i=1; i < endpoint; i++){ if(_dataDTZ[startpoint+i] == 0x29) endpoint = 1; // break if appear ) else meterNumber += _dataDTZ[startpoint+i]; } int numberLength = meterNumber.length(); if (numberLength <= 10) MeterID[_MID0] = meterNumber.toInt(); else { endpoint = numberLength-7; // get meter manu before meter-number MeterID[_MID0] = meterNumber.substring(endpoint-1).toInt(); valueType = 1; } // Serial.println(meterNumber); // debug } if(valueType == 1){ //meter manufracture int oneChar = 0; MeterID[_MID2] = 0x000000A0; for(int i=1; i < endpoint; i++){ oneChar = _dataDTZ[startpoint+i]; if(oneChar == 0x29) {endpoint = 1; break;} // 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; } } } if(valueType == 0x50){ String valueSing = "0"; int i; int dot=0; bool negative = false; for(i=1; i < endpoint; i++){ if(_dataDTZ[startpoint+i] == 0x2a) endpoint = 1; // break if appear * if(dot != 0 && i-dot > 4){ endpoint = 1; i++; break;} // break if Nachkommastelle größer 4, wegen Überlauf int_32 if(_dataDTZ[startpoint+i] == 0x2e) dot=i; if(_dataDTZ[startpoint+i] == 0x2d) negative = true; // check if negativ value if(_dataDTZ[startpoint+i] >= 0x30 && _dataDTZ[startpoint+i] <= 0x39) valueSing += _dataDTZ[startpoint+i]; } result = valueSing.toInt(); if(negative) result *= -1; int dotx=i-dot-2; if(dot>0){ switch(dotx){ // calibrate all variants of values into one value type and potence according unitScale 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 // Serial.print(result); Serial.print("-"); Serial.print(dotx); Serial.print(":"); Serial.println(dot); //debug } } return result; } //*********************** decode data from Logarex LX13BE ************ int decodeMeter::decodeDataDIN(int pointer){ if (_dataDTZ[pointer] == 0x0a && _dataDTZ[pointer-1] == 0x0d && _dataDTZ[pointer-2] == 0x21){ // check of protocol end int32_t result= -1; getOneValueDIN(SearchOBIS("96.1.0*255",9),18,0,1); // Zählernummer and manufracture result = getOneValueDIN(SearchOBIS("1.8.0*255",8),20,0x50,10000); // Bezug fromGrid if(result > 0) MeterKWH[_FROMGRID_0]=result; else MeterKWH[_FROMGRID_0]=0; result = getOneValueDIN(SearchOBIS("1.8.1*255",8),20,0x50,10000); // Bezug fromGrid if(result > 0) MeterKWH[_FROMGRID_1]=result; else MeterKWH[_FROMGRID_1]=0; result = getOneValueDIN(SearchOBIS("1.8.2*255",8),20,0x50,10000); // Bezug fromGrid if(result > 0) MeterKWH[_FROMGRID_2]=result; else MeterKWH[_FROMGRID_2]=0; result = getOneValueDIN(SearchOBIS("2.8.0*255",8),20,0x50,10000); // EEG intoGrid if(result > 0) MeterKWH[_FEEDIN]=result; else MeterKWH[_FEEDIN]=0; result = getOneValueDIN(SearchOBIS("16.7.0*255",9),18,0x50,1); // P in W if(result != 0) Electric_data[_CONSUMPTION]=result; else Electric_data[_CONSUMPTION]=0; result = getOneValueDIN(SearchOBIS("32.7.0*255",9),18,0x50,10); // UL1 if(result != 0) Electric_data[_U_L1]=result; else Electric_data[_U_L1]=0; result = getOneValueDIN(SearchOBIS("52.7.0*255",9),18,0x50,10); // UL3 if(result != 0) Electric_data[_U_L2]=result; else Electric_data[_U_L2]=0; result = getOneValueDIN(SearchOBIS("72.7.0*255",9),18,0x50,10); // UL3 if(result != 0) Electric_data[_U_L3]=result; else Electric_data[_U_L3]=0; result = getOneValueDIN(SearchOBIS("31.7.0*255",9),18,0x50,100); // IL1 if(result != 0) Electric_data[_I_L1]=result; else Electric_data[_I_L1]=0; result = getOneValueDIN(SearchOBIS("51.7.0*255",9),18,0x50,100); // IL2 if(result != 0) Electric_data[_I_L2]=result; else Electric_data[_I_L2]=0; result = getOneValueDIN(SearchOBIS("71.7.0*255",9),18,0x50,100); // IL3 if(result != 0) Electric_data[_I_L3]=result; else Electric_data[_I_L3]=0; result = getOneValueDIN(SearchOBIS("36.7.0*255",9),18,0x50,1); // PL1 if(result != 0) Electric_data[_P_L1]=result; else Electric_data[_P_L1]=0; result = getOneValueDIN(SearchOBIS("56.7.0*255",9),18,0x50,1); // PL2 if(result != 0) Electric_data[_P_L2]=result; else Electric_data[_P_L2]=0; result = getOneValueDIN(SearchOBIS("76.7.0*255",9),18,0x50,1); // PL3 if(result != 0) Electric_data[_P_L3]=result; else Electric_data[_P_L3]=0; result = getOneValueDIN(SearchOBIS("14.7.0*255",9),18,0x50,10); // Freq if(result != 0) Electric_data[_FREQ]=result; else Electric_data[_FREQ]=0; ProtocolCounter ++; // count the protocols _ShowProtocolTime = 200; //************** protocol send debug only ****************/ if(SendProtocols > 0){ send2dbasePOST(sendProtokollDebug(),false); //debug SendProtocols --; } //************** protocol send debug end *****************/ pointer = 0; // clear for new begin of protocol } pointer ++; return pointer; } #endif //*** DECODE_METER_H_