/** * DLMS (encrypted mBus stream) decode StromLog * Version 7.04 * 28.06.2024 P. Rebesky * author Creator P.Rebesky * Copyright (©): 2022-2032 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 SIMULATION // means 9600 8N1 serialspeed #define mBUS_WAIT 0 #define CRYPTMARK 1 #define SYSTEMTITLE 2 #define PAYLOADLENGTH 3 #define PAYLOAD2LENGTH 0x82 #define STARTFRAME 4 #define GETPAYLOAD 5 #define mBUS_END 16 #include "global.h" #include #include char METERID[]= {0x00,0xff,0x09,0x10}; char OBIS180[]= {0x01,0x08,0x00,0xff}; char OBIS181[]= {0x01,0x08,0x01,0xff}; char OBIS182[]= {0x01,0x08,0x02,0xff}; char OBIS280[]= {0x02,0x08,0x00,0xff}; char OBISpPlus[]= {0x01,0x07,0x00,0xff}; char OBISpMinus[]= {0x02,0x07,0x00,0xff}; char OBIS_U1[]= {0x20,0x07,0x00,0xff}; char OBIS_U2[]= {0x34,0x07,0x00,0xff}; char OBIS_U3[]= {0x48,0x07,0x00,0xff}; char OBIS_I1[]= {0x1F,0x07,0x00,0xff}; char OBIS_I2[]= {0x33,0x07,0x00,0xff}; char OBIS_I3[]= {0x47,0x07,0x00,0xff}; char OBIS_LF[]= {0x0d,0x07,0x00,0xff}; extern bool send2dbasePOST(String content, bool saveIfError); extern String sendProtokollDebug(); AES128 aes128; /***** 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[10]; 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[10]; 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 ValueScale; int BufferLength; uint PayLoadLength; int ByteCounter=0; int MBusFrameCount=0; int ProtStatus=mBUS_WAIT; // uint32_t value0; uint32_t value1; uint32_t value2; uint32_t TimeMarker=0; uint16_t CRC_ccitt; uint16_t CRC_meter; int SystemFramePointer=0; // byte Key[16]={0x36,0xC6,0x66,0x39,0xE4,0x8A,0x8C,0xA4,0xD6,0xBC,0x8B,0x28,0x2A,0x79,0x3B,0xBB}; byte Key[16]={0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF}; byte Key4Xor[16]; byte SystemFrame[16]; char ExtractValue[14]; byte CipherBlock[400]; // buffer for load cipher before and decript after void HandleDataStream(); void ShowCipherBlock(); void ShowSystemFrame(); void DecryptData(uint cryptLength); void DecodeData(); uint32_t DecodeValue(int pointer); int GetScaler(int pointer); int DecodeMeterNumber(); int CheckIfDataValide(); void GetManufracturer(); int SearchOBIS(char *searchCode); int CheckOBIS(char *checkCode, int i); public: uint32_t ErrorCounter=0; uint32_t ProtocolCounter=0; int SendProtocols=0; int Prot_Type; int SerialSpeed=10; String MeterType = NOT_DETECT; void Begin(int maxBuffer); String getProtocolType(); int mainEntryDecode(); int setKey(String newKey); String getKey(); void clearValues(); int getErrorsPercent(); void sendRequest2Meter(); void setSerialSpeed(); 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; } //**** set speed of RS232 ******************************************** void decodeMeter::setSerialSpeed(){ #ifdef SIMULATION Serial.begin(9600,SERIAL_8N1); // for debug only #else Serial.begin(2400,SERIAL_8N1); #endif } //**** send a request to meter -> only for old types like AS1440 or else types ****/ void decodeMeter::sendRequest2Meter(){ // Serial.println("/?!"); // is not required for SML, DLMS/COSEM or new meter-types -> also do nothing } //***** return protocol version ************************************** String decodeMeter::getProtocolType(){ // it's in user for build-versions-info return "DLMS"; } //***** calculate error in percent *********************************** int decodeMeter::getErrorsPercent(){ if(ProtocolCounter > 0){ return ErrorCounter*100/ProtocolCounter; } else return 0; } //***** clear all electrics data ************************************* void decodeMeter::clearValues(){ for (int i=0;i<10;i++){ Electric_data[i]=0; } } //**** calculate crc sum of protocol ********************************* void decodeMeter::crc_add(uint16_t oneByte){ // is not necessary because of meter-key } //**** handle set or get meter key for DLMS encrypted data stream **** int decodeMeter::setKey(String newKey){ int result=0; String oneByte; char convertHex[2]; if(newKey.length()==32){ for(int i=0;i=3 && _dataPointer < 10){ // prot at begin? if(_dataDTZ[_dataPointer]==0x68 && _dataDTZ[_dataPointer-3]==0x68){ // if start sequence available MBusFrameCount=_dataDTZ[_dataPointer-2]; // one Block of mBus _ShowProtocolTime = 200; // flash red LED ProtStatus=CRYPTMARK; // mark begin of protocol } } else if(ProtStatus==CRYPTMARK){ // get system title 0xDB08 if(_dataDTZ[_dataPointer]==0x08 && _dataDTZ[_dataPointer-1]==0xdb){ // meter sign and framebegin ProtStatus=SYSTEMTITLE; SystemFramePointer=0; } } else if(ProtStatus==SYSTEMTITLE){ // application layer if(SystemFramePointer < 8){ // System title length = 8 SystemFrame[SystemFramePointer]=_dataDTZ[_dataPointer]; SystemFramePointer++; } else { PayLoadLength =_dataDTZ[_dataPointer]; // save value of layer into PayLoadLength ProtStatus=PAYLOADLENGTH; } } else if(ProtStatus==PAYLOADLENGTH){ // application layer length of payload // Serial.print("Length Payload:"); if(PayLoadLength==0x82) ProtStatus=PAYLOAD2LENGTH; // length field two byte else ProtStatus=STARTFRAME; // else one byte only PayLoadLength=_dataDTZ[_dataPointer]; ByteCounter=0; // set byte counter at begin } else if(ProtStatus==PAYLOAD2LENGTH){ PayLoadLength=PayLoadLength*256; PayLoadLength += _dataDTZ[_dataPointer]; ByteCounter=0; // set byte counter at begin ProtStatus=STARTFRAME; } else if(ProtStatus==STARTFRAME){ ByteCounter++; if(ByteCounter>=2 && ByteCounter<6 && SystemFramePointer=6){ ByteCounter=0; CipherBlock[ByteCounter++]=_dataDTZ[_dataPointer]; // save the first byte!!! ProtStatus=GETPAYLOAD; // Serial.println(PayLoadLength); // ShowSystemFrame(); } } else if (ProtStatus==GETPAYLOAD){ if(ByteCounter<=PayLoadLength-6 && ByteCounter < sizeof(CipherBlock)){ if(MBusFrameCount>=0){ CipherBlock[ByteCounter]=_dataDTZ[_dataPointer]; ByteCounter++; } else { if(_dataDTZ[_dataPointer-5]==0x68 && _dataDTZ[_dataPointer-8]==0x68){ // if start sequence available MBusFrameCount=_dataDTZ[_dataPointer-7]; // second Block of mBus } } } else { ProtStatus=mBUS_END; byte checkSumByte=_dataDTZ[_dataPointer]; // Serial.println(_dataDTZ[_dataPointer]); // CS-Byte (Checksum) } //Serial.println(ByteCounter); } else if(ProtStatus==mBUS_END){ ProtocolCounter++; // ShowCipherBlock(); DecryptData(PayLoadLength); if(CheckIfDataValide())DecodeData(); else ErrorCounter++; ProtStatus=mBUS_WAIT; // set on start again // Serial.println(PayLoadLength); //************** protokoll debug only ****************/ if(SendProtocols > 0){ send2dbasePOST(sendProtokollDebug(),false); //debug SendProtocols --; } //************** protokoll debug end *****************/ } _dataPointer++; // Serial.println(_dataPointer); return 0; } //**** decode decrypted payload to values ***************** void decodeMeter::DecodeData(){ // Serial.println(PayLoadLength); MeterKWH[_FROMGRID_0]=DecodeValue(SearchOBIS(OBIS180)); MeterKWH[_FROMGRID_1]=DecodeValue(SearchOBIS(OBIS181)); MeterKWH[_FROMGRID_2]=DecodeValue(SearchOBIS(OBIS182)); MeterKWH[_FEEDIN]=DecodeValue(SearchOBIS(OBIS280)); Electric_data[_CONSUMPTION]=(DecodeValue(SearchOBIS(OBISpPlus))/10)-(DecodeValue(SearchOBIS(OBISpMinus))/10); Electric_data[_U_L1]=DecodeValue(SearchOBIS(OBIS_U1)); Electric_data[_U_L2]=DecodeValue(SearchOBIS(OBIS_U2)); Electric_data[_U_L3]=DecodeValue(SearchOBIS(OBIS_U3)); Electric_data[_I_L1]=DecodeValue(SearchOBIS(OBIS_I1)); Electric_data[_I_L2]=DecodeValue(SearchOBIS(OBIS_I2)); Electric_data[_I_L3]=DecodeValue(SearchOBIS(OBIS_I3)); // DecodeValue(SearchOBIS(OBIS_LF)); DecodeMeterNumber(); } //**** serch OBIS code ************************************* int decodeMeter::SearchOBIS(char *searchCode){ int x = 0; int slength=sizeof(searchCode)-1; do { if (*searchCode == CipherBlock[x]){ if (CheckOBIS(searchCode, x)==0)break; } x++; } while (x < PayLoadLength && x < sizeof(CipherBlock)); // Serial.println(x); if(x > PayLoadLength - 1)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=sizeof(checkCode)-1; for (int y=0;y<=searchLength;y++){ if(*(checkCode+y) != CipherBlock[i+y]) return -1; // check chars if identical } return 0; } //**** get values of meter - be careful of this recursive function! **** uint32_t decodeMeter::DecodeValue(int pointer){ int scaler=1; // set scale of default 1 if(pointer==0) return 0; // OBIS not included, no value available set zero uint32_t singleValue=0; int l; // pointer of count length of number. 2,4 or 8 length // Serial.println(CipherBlock[pointer]); switch(CipherBlock[pointer]){ case 0x05: l=4; break; case 0x06: l=4; break; case 0x12: l=2; break; case 0x0f: l=1; break; case 0x10: l=2; break; case 0x11: l=1; break; case 0x14: l=8; break; case 0x15: l=8; break; default: return 0; } for(int i=1; i<=l;i++){ singleValue = singleValue << 8; //shift bits 8 times to left singleValue +=CipherBlock[pointer+i]; } scaler=GetScaler(pointer+l); // Serial.println(singleValue); return singleValue*scaler; } //**** get scaler of single value ************************** int decodeMeter::GetScaler(int pointer){ byte scaler=CipherBlock[pointer+4]; switch(scaler){ case 0x01: return 100; case 0x00: return 10; case 0xff: return 1; case 0xfe: return 1; case 0xfd: return 1; } return 1; // default return one for scaler } //**** decode meter number ********************************* int decodeMeter::DecodeMeterNumber(){ // its not the real meter number but all bits are using int targetNumber=SearchOBIS(METERID); char numberPart; String meterNumber=""; if(targetNumber>0){ // if there found a result for(int i=7; i<16;i++){ // decode the last ten numbers only numberPart = CipherBlock[targetNumber+i]; meterNumber += numberPart; } MeterID[_MID0]=meterNumber.toInt(); MeterID[_MID2]=0x0A01; // standard ID for number of meters in germany. it's the begin for(int i=0;i<3;i++){ // get and save the manufracturer ID MeterID[_MID1]=MeterID[_MID1] << 8; MeterID[_MID1]=MeterID[_MID1] | MeterType[i]; } MeterID[_MID1]=MeterID[_MID1] << 8; numberPart = CipherBlock[targetNumber+1]; // decode the first two bytes of number, because the real number is bigger than 32 bits :-( meterNumber = numberPart; numberPart = CipherBlock[targetNumber+2]; meterNumber += numberPart; MeterID[_MID1]=MeterID[_MID1] | meterNumber.toInt(); } // Serial.println(meterNumber); return 0; } //**** check if data are valide and correct decrypted ****** int decodeMeter::CheckIfDataValide(){ int result=0; GetManufracturer(); if(CipherBlock[0] == 0x0f && (CipherBlock[1]&0x7f)==0x00){ // not sure!!! could be 0x80h MeterType += " (Key OK)"; result=1; } else MeterType += " false Key!"; return result; } //**** get and write manufracturer into variable *********** void decodeMeter::GetManufracturer(){ MeterType=""; for(int i=0;i<3;i++){ char single=SystemFrame[i]; MeterType+=single; // Serial.println(single); } } //**** show decrypted data on serial *********************** void decodeMeter::ShowCipherBlock(){ for(int i=0; i